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: Date.now(), // the timestamp of the last resize request
254 last_page_scroll: 0, // the timestamp the last time the page was scrolled
256 // the current profile
257 // we may have many...
259 pixels_per_point: 1, // the minimum pixels per point for all charts
260 // increase this to speed javascript up
261 // each chart library has its own limit too
262 // the max of this and the chart library is used
263 // the final is calculated every time, so a change
264 // here will have immediate effect on the next chart
267 idle_between_charts: 100, // ms - how much time to wait between chart updates
269 fast_render_timeframe: 200, // ms - render continously until this time of continious
270 // rendering has been reached
271 // this setting is used to make it render e.g. 10
272 // charts at once, sleep idle_between_charts time
273 // and continue for another 10 charts.
275 idle_between_loops: 500, // ms - if all charts have been updated, wait this
276 // time before starting again.
278 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
280 idle_lost_focus: 500, // ms - when the window does not have focus, check
281 // if focus has been regained, every this time
283 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
284 // autorefreshing of charts is paused for this amount
287 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
288 // of time before setting up synchronized selections
291 sync_selection: true, // enable or disable selection sync
293 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
295 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
297 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
299 update_only_visible: true, // enable or disable visibility management
301 parallel_refresher: true, // enable parallel refresh of charts
303 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
305 destroy_on_hide: false, // destroy charts when they are not visible
307 show_help: netdataShowHelp, // when enabled the charts will show some help
308 show_help_delay_show_ms: 500,
309 show_help_delay_hide_ms: 0,
311 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
313 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
314 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
316 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
318 smooth_plot: true, // enable smooth plot, where possible
320 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
322 color_fill_opacity_line: 1.0,
323 color_fill_opacity_area: 0.2,
324 color_fill_opacity_stacked: 0.8,
326 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
327 pan_and_zoom_factor_multiplier_control: 2.0,
328 pan_and_zoom_factor_multiplier_shift: 3.0,
329 pan_and_zoom_factor_multiplier_alt: 4.0,
331 abort_ajax_on_scroll: false, // kill pending ajax page scroll
332 async_on_scroll: false, // sync/async onscroll handler
333 onscroll_worker_duration_threshold: 30, // time in ms, to consider slow the onscroll handler
335 setOptionCallback: function() { ; }
343 chart_data_url: false,
344 chart_errors: false, // FIXME
352 NETDATA.statistics = {
355 refreshes_active_max: 0
359 // ----------------------------------------------------------------------------------------------------------------
360 // local storage options
362 NETDATA.localStorage = {
365 callback: {} // only used for resetting back to defaults
368 NETDATA.localStorageGet = function(key, def, callback) {
371 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
372 NETDATA.localStorage.default[key.toString()] = def;
373 NETDATA.localStorage.callback[key.toString()] = callback;
376 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
378 // console.log('localStorage: loading "' + key.toString() + '"');
379 ret = localStorage.getItem(key.toString());
380 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
381 if(ret === null || ret === 'undefined') {
382 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
383 localStorage.setItem(key.toString(), JSON.stringify(def));
387 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
388 ret = JSON.parse(ret);
389 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
393 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
398 if(typeof ret === 'undefined' || ret === 'undefined') {
399 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
403 NETDATA.localStorage.current[key.toString()] = ret;
407 NETDATA.localStorageSet = function(key, value, callback) {
408 if(typeof value === 'undefined' || value === 'undefined') {
409 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
412 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
413 NETDATA.localStorage.default[key.toString()] = value;
414 NETDATA.localStorage.current[key.toString()] = value;
415 NETDATA.localStorage.callback[key.toString()] = callback;
418 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
419 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
421 localStorage.setItem(key.toString(), JSON.stringify(value));
424 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
428 NETDATA.localStorage.current[key.toString()] = value;
432 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
434 if(typeof obj[i] === 'object') {
435 //console.log('object ' + prefix + '.' + i.toString());
436 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
440 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
444 NETDATA.setOption = function(key, value) {
445 if(key.toString() === 'setOptionCallback') {
446 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
447 NETDATA.options.current[key.toString()] = value;
448 NETDATA.options.current.setOptionCallback();
451 else if(NETDATA.options.current[key.toString()] !== value) {
452 var name = 'options.' + key.toString();
454 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
455 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
457 //console.log(NETDATA.localStorage);
458 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
459 //console.log(NETDATA.options);
460 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
462 if(typeof NETDATA.options.current.setOptionCallback === 'function')
463 NETDATA.options.current.setOptionCallback();
469 NETDATA.getOption = function(key) {
470 return NETDATA.options.current[key.toString()];
473 // read settings from local storage
474 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
476 // always start with this option enabled.
477 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
479 NETDATA.resetOptions = function() {
480 for(var i in NETDATA.localStorage.default) {
481 var a = i.split('.');
483 if(a[0] === 'options') {
484 if(a[1] === 'setOptionCallback') continue;
485 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
486 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
488 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
490 else if(a[0] === 'chart_heights') {
491 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
492 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
498 // ----------------------------------------------------------------------------------------------------------------
500 if(NETDATA.options.debug.main_loop === true)
501 console.log('welcome to NETDATA');
503 NETDATA.onresize = function() {
504 NETDATA.options.last_resized = Date.now();
508 NETDATA.onscroll_updater_count = 0;
509 NETDATA.onscroll_updater_running = false;
510 NETDATA.onscroll_updater_last_run = 0;
511 NETDATA.onscroll_updater_watchdog = null;
512 NETDATA.onscroll_updater_max_duration = 0;
513 NETDATA.onscroll_updater_above_threshold_count = 0;
514 NETDATA.onscroll_updater = function() {
515 NETDATA.onscroll_updater_running = true;
516 NETDATA.onscroll_updater_count++;
517 var start = Date.now();
519 var targets = NETDATA.options.targets;
520 var len = targets.length;
522 // when the user scrolls he sees that we have
523 // hidden all the not-visible charts
524 // using this little function we try to switch
525 // the charts back to visible quickly
528 if(NETDATA.options.abort_ajax_on_scroll === true) {
529 // we have to cancel pending requests too
532 if (targets[len]._updating === true) {
533 if (typeof targets[len].xhr !== 'undefined') {
534 targets[len].xhr.abort();
535 targets[len].running = false;
536 targets[len]._updating = false;
538 targets[len].isVisible();
543 // just find which chart is visible
546 targets[len].isVisible();
549 var end = Date.now();
550 // console.log('scroll No ' + NETDATA.onscroll_updater_count + ' calculation took ' + (end - start).toString() + ' ms');
552 if(NETDATA.options.current.async_on_scroll === false) {
553 var dt = end - start;
554 if(dt > NETDATA.onscroll_updater_max_duration) {
555 // console.log('max onscroll event handler duration increased to ' + dt);
556 NETDATA.onscroll_updater_max_duration = dt;
559 if(dt > NETDATA.options.current.onscroll_worker_duration_threshold) {
560 // console.log('slow: ' + dt);
561 NETDATA.onscroll_updater_above_threshold_count++;
563 if(NETDATA.onscroll_updater_above_threshold_count > 2 && NETDATA.onscroll_updater_above_threshold_count * 100 / NETDATA.onscroll_updater_count > 2) {
564 NETDATA.setOption('async_on_scroll', true);
565 console.log('NETDATA: your browser is slow - enabling asynchronous onscroll event handler.');
570 NETDATA.onscroll_updater_last_run = start;
571 NETDATA.onscroll_updater_running = false;
574 NETDATA.onscroll = function() {
575 // console.log('onscroll');
577 NETDATA.options.last_page_scroll = Date.now();
578 NETDATA.options.auto_refresher_stop_until = 0;
580 if(NETDATA.options.targets === null) return;
582 if(NETDATA.options.current.async_on_scroll === true) {
584 if(NETDATA.onscroll_updater_running === false) {
585 NETDATA.onscroll_updater_running = true;
586 setTimeout(NETDATA.onscroll_updater, 0);
589 if(NETDATA.onscroll_updater_watchdog !== null)
590 clearTimeout(NETDATA.onscroll_updater_watchdog);
592 NETDATA.onscroll_updater_watchdog = setTimeout(function() {
593 if(NETDATA.onscroll_updater_running === false && NETDATA.options.last_page_scroll > NETDATA.onscroll_updater_last_run) {
594 // console.log('watchdog');
595 NETDATA.onscroll_updater();
598 NETDATA.onscroll_updater_watchdog = null;
604 NETDATA.onscroll_updater();
608 window.onresize = NETDATA.onresize;
609 window.onscroll = NETDATA.onscroll;
611 // ----------------------------------------------------------------------------------------------------------------
614 NETDATA.errorCodes = {
615 100: { message: "Cannot load chart library", alert: true },
616 101: { message: "Cannot load jQuery", alert: true },
617 402: { message: "Chart library not found", alert: false },
618 403: { message: "Chart library not enabled/is failed", alert: false },
619 404: { message: "Chart not found", alert: false },
620 405: { message: "Cannot download charts index from server", alert: true },
621 406: { message: "Invalid charts index downloaded from server", alert: true },
622 407: { message: "Cannot HELLO netdata server", alert: false },
623 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
624 409: { message: "Cannot ACCESS netdata registry", alert: false },
625 410: { message: "Netdata registry ACCESS failed", alert: false },
626 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
627 412: { message: "Netdata registry DELETE failed", alert: false },
628 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
629 414: { message: "Netdata registry SWITCH failed", alert: false },
630 415: { message: "Netdata alarms download failed", alert: false },
631 416: { message: "Netdata alarms log download failed", alert: false },
632 417: { message: "Netdata registry server send invalid response to SEARCH ", alert: false },
633 418: { message: "Netdata registry SEARCH failed", alert: false }
635 NETDATA.errorLast = {
641 NETDATA.error = function(code, msg) {
642 NETDATA.errorLast.code = code;
643 NETDATA.errorLast.message = msg;
644 NETDATA.errorLast.datetime = Date.now();
646 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
649 if(typeof netdataErrorCallback === 'function') {
650 ret = netdataErrorCallback('system', code, msg);
653 if(ret && NETDATA.errorCodes[code].alert)
654 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
657 NETDATA.errorReset = function() {
658 NETDATA.errorLast.code = 0;
659 NETDATA.errorLast.message = "You are doing fine!";
660 NETDATA.errorLast.datetime = 0;
663 // ----------------------------------------------------------------------------------------------------------------
664 // commonMin & commonMax
666 NETDATA.commonMin = {
670 get: function(state) {
671 if(typeof state.__commonMin === 'undefined') {
672 // get the commonMin setting
673 var self = $(state.element);
674 state.__commonMin = self.data('common-min') || null;
677 var min = state.data.min;
678 var name = state.__commonMin;
681 // we don't need commonMin
682 //state.log('no need for commonMin');
686 var t = this.keys[name];
687 if(typeof t === 'undefined') {
689 this.keys[name] = {};
693 var uuid = state.uuid;
694 if(typeof t[uuid] !== 'undefined') {
695 if(t[uuid] === min) {
696 //state.log('commonMin ' + state.__commonMin + ' not changed: ' + this.latest[name]);
697 return this.latest[name];
699 else if(min < this.latest[name]) {
700 //state.log('commonMin ' + state.__commonMin + ' increased: ' + min);
702 this.latest[name] = min;
710 // find the common min
713 if(t[i] < m) m = t[i];
715 //state.log('commonMin ' + state.__commonMin + ' updated: ' + m);
716 this.latest[name] = m;
721 NETDATA.commonMax = {
725 get: function(state) {
726 if(typeof state.__commonMax === 'undefined') {
727 // get the commonMax setting
728 var self = $(state.element);
729 state.__commonMax = self.data('common-max') || null;
732 var max = state.data.max;
733 var name = state.__commonMax;
736 // we don't need commonMax
737 //state.log('no need for commonMax');
741 var t = this.keys[name];
742 if(typeof t === 'undefined') {
744 this.keys[name] = {};
748 var uuid = state.uuid;
749 if(typeof t[uuid] !== 'undefined') {
750 if(t[uuid] === max) {
751 //state.log('commonMax ' + state.__commonMax + ' not changed: ' + this.latest[name]);
752 return this.latest[name];
754 else if(max > this.latest[name]) {
755 //state.log('commonMax ' + state.__commonMax + ' increased: ' + max);
757 this.latest[name] = max;
765 // find the common max
768 if(t[i] > m) m = t[i];
770 //state.log('commonMax ' + state.__commonMax + ' updated: ' + m);
771 this.latest[name] = m;
776 // ----------------------------------------------------------------------------------------------------------------
779 // When multiple charts need the same chart, we avoid downloading it
780 // multiple times (and having it in browser memory multiple time)
781 // by using this registry.
783 // Every time we download a chart definition, we save it here with .add()
784 // Then we try to get it back with .get(). If that fails, we download it.
786 NETDATA.chartRegistry = {
789 fixid: function(id) {
790 return id.replace(/:/g, "_").replace(/\//g, "_");
793 add: function(host, id, data) {
794 host = this.fixid(host);
797 if(typeof this.charts[host] === 'undefined')
798 this.charts[host] = {};
800 //console.log('added ' + host + '/' + id);
801 this.charts[host][id] = data;
804 get: function(host, id) {
805 host = this.fixid(host);
808 if(typeof this.charts[host] === 'undefined')
811 if(typeof this.charts[host][id] === 'undefined')
814 //console.log('cached ' + host + '/' + id);
815 return this.charts[host][id];
818 downloadAll: function(host, callback) {
819 while(host.slice(-1) === '/')
820 host = host.substring(0, host.length - 1);
825 url: host + '/api/v1/charts',
828 xhrFields: { withCredentials: true } // required for the cookie
830 .done(function(data) {
832 var h = NETDATA.chartRegistry.fixid(host);
833 self.charts[h] = data.charts;
835 else NETDATA.error(406, host + '/api/v1/charts');
837 if(typeof callback === 'function')
841 NETDATA.error(405, host + '/api/v1/charts');
843 if(typeof callback === 'function')
849 // ----------------------------------------------------------------------------------------------------------------
850 // Global Pan and Zoom on charts
852 // Using this structure are synchronize all the charts, so that
853 // when you pan or zoom one, all others are automatically refreshed
854 // to the same timespan.
856 NETDATA.globalPanAndZoom = {
857 seq: 0, // timestamp ms
858 // every time a chart is panned or zoomed
859 // we set the timestamp here
860 // then we use it as a sequence number
861 // to find if other charts are syncronized
864 master: null, // the master chart (state), to which all others
867 force_before_ms: null, // the timespan to sync all other charts
868 force_after_ms: null,
873 setMaster: function(state, after, before) {
874 if(NETDATA.options.current.sync_pan_and_zoom === false)
877 if(this.master !== null && this.master !== state)
878 this.master.resetChart(true, true);
880 var now = Date.now();
883 this.force_after_ms = after;
884 this.force_before_ms = before;
885 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
887 if(typeof this.callback === 'function')
888 this.callback(true, after, before);
892 clearMaster: function() {
893 if(this.master !== null) {
894 var st = this.master;
901 this.force_after_ms = null;
902 this.force_before_ms = null;
903 NETDATA.options.auto_refresher_stop_until = 0;
905 if(typeof this.callback === 'function')
906 this.callback(false, 0, 0);
909 // is the given state the master of the global
910 // pan and zoom sync?
911 isMaster: function(state) {
912 if(this.master === state) return true;
916 // are we currently have a global pan and zoom sync?
917 isActive: function() {
918 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
922 // check if a chart, other than the master
923 // needs to be refreshed, due to the global pan and zoom
924 shouldBeAutoRefreshed: function(state) {
925 if(this.master === null || this.seq === 0)
928 //if(state.needsRecreation())
931 if(state.tm.pan_and_zoom_seq === this.seq)
938 // ----------------------------------------------------------------------------------------------------------------
939 // dimensions selection
942 // move color assignment to dimensions, here
944 dimensionStatus = function(parent, label, name_div, value_div, color) {
945 this.enabled = false;
946 this.parent = parent;
948 this.name_div = null;
949 this.value_div = null;
950 this.color = NETDATA.themes.current.foreground;
952 if(parent.unselected_count === 0)
953 this.selected = true;
955 this.selected = false;
957 this.setOptions(name_div, value_div, color);
960 dimensionStatus.prototype.invalidate = function() {
961 this.name_div = null;
962 this.value_div = null;
963 this.enabled = false;
966 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
969 if(this.name_div != name_div) {
970 this.name_div = name_div;
971 this.name_div.title = this.label;
972 this.name_div.style.color = this.color;
973 if(this.selected === false)
974 this.name_div.className = 'netdata-legend-name not-selected';
976 this.name_div.className = 'netdata-legend-name selected';
979 if(this.value_div != value_div) {
980 this.value_div = value_div;
981 this.value_div.title = this.label;
982 this.value_div.style.color = this.color;
983 if(this.selected === false)
984 this.value_div.className = 'netdata-legend-value not-selected';
986 this.value_div.className = 'netdata-legend-value selected';
993 dimensionStatus.prototype.setHandler = function() {
994 if(this.enabled === false) return;
998 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
999 this.name_div.onclick = this.value_div.onclick = function(e) {
1001 if(ds.isSelected()) {
1003 if(e.shiftKey === true || e.ctrlKey === true) {
1004 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
1007 if(ds.parent.countSelected() === 0)
1008 ds.parent.selectAll();
1011 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
1012 if(ds.parent.countSelected() === 1) {
1013 ds.parent.selectAll();
1016 ds.parent.selectNone();
1022 // this is not selected
1023 if(e.shiftKey === true || e.ctrlKey === true) {
1024 // control or shift key is pressed -> select this too
1028 // no key is pressed -> select only this
1029 ds.parent.selectNone();
1034 ds.parent.state.redrawChart();
1038 dimensionStatus.prototype.select = function() {
1039 if(this.enabled === false) return;
1041 this.name_div.className = 'netdata-legend-name selected';
1042 this.value_div.className = 'netdata-legend-value selected';
1043 this.selected = true;
1046 dimensionStatus.prototype.unselect = function() {
1047 if(this.enabled === false) return;
1049 this.name_div.className = 'netdata-legend-name not-selected';
1050 this.value_div.className = 'netdata-legend-value hidden';
1051 this.selected = false;
1054 dimensionStatus.prototype.isSelected = function() {
1055 return(this.enabled === true && this.selected === true);
1058 // ----------------------------------------------------------------------------------------------------------------
1060 dimensionsVisibility = function(state) {
1063 this.dimensions = {};
1064 this.selected_count = 0;
1065 this.unselected_count = 0;
1068 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
1069 if(typeof this.dimensions[label] === 'undefined') {
1071 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
1074 this.dimensions[label].setOptions(name_div, value_div, color);
1076 return this.dimensions[label];
1079 dimensionsVisibility.prototype.dimensionGet = function(label) {
1080 return this.dimensions[label];
1083 dimensionsVisibility.prototype.invalidateAll = function() {
1084 for(var d in this.dimensions)
1085 this.dimensions[d].invalidate();
1088 dimensionsVisibility.prototype.selectAll = function() {
1089 for(var d in this.dimensions)
1090 this.dimensions[d].select();
1093 dimensionsVisibility.prototype.countSelected = function() {
1095 for(var d in this.dimensions)
1096 if(this.dimensions[d].isSelected()) i++;
1101 dimensionsVisibility.prototype.selectNone = function() {
1102 for(var d in this.dimensions)
1103 this.dimensions[d].unselect();
1106 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
1107 var ret = new Array();
1108 this.selected_count = 0;
1109 this.unselected_count = 0;
1111 var len = array.length;
1113 var ds = this.dimensions[array[len]];
1114 if(typeof ds === 'undefined') {
1115 // console.log(array[i] + ' is not found');
1118 else if(ds.isSelected()) {
1120 this.selected_count++;
1124 this.unselected_count++;
1128 if(this.selected_count === 0 && this.unselected_count !== 0) {
1130 return this.selected2BooleanArray(array);
1137 // ----------------------------------------------------------------------------------------------------------------
1138 // global selection sync
1140 NETDATA.globalSelectionSync = {
1142 dont_sync_before: 0,
1147 if(this.state !== null)
1148 this.state.globalSelectionSyncStop();
1152 if(this.state !== null) {
1153 this.state.globalSelectionSyncDelay();
1158 // ----------------------------------------------------------------------------------------------------------------
1159 // Our state object, where all per-chart values are stored
1161 chartState = function(element) {
1162 var self = $(element);
1163 this.element = element;
1166 // all private functions should use 'that', instead of 'this'
1169 /* error() - private
1170 * show an error instead of the chart
1172 var error = function(msg) {
1175 if(typeof netdataErrorCallback === 'function') {
1176 ret = netdataErrorCallback('chart', that.id, msg);
1180 that.element.innerHTML = that.id + ': ' + msg;
1181 that.enabled = false;
1182 that.current = that.pan;
1186 // GUID - a unique identifier for the chart
1187 this.uuid = NETDATA.guid();
1189 // string - the name of chart
1190 this.id = self.data('netdata');
1192 // string - the key for localStorage settings
1193 this.settings_id = self.data('id') || null;
1195 // the user given dimensions of the element
1196 this.width = self.data('width') || NETDATA.chartDefaults.width;
1197 this.height = self.data('height') || NETDATA.chartDefaults.height;
1199 if(this.settings_id !== null) {
1200 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1201 // this is the callback that will be called
1202 // if and when the user resets all localStorage variables
1203 // to their defaults
1205 resizeChartToHeight(height);
1209 // string - the netdata server URL, without any path
1210 this.host = self.data('host') || NETDATA.chartDefaults.host;
1212 // make sure the host does not end with /
1213 // all netdata API requests use absolute paths
1214 while(this.host.slice(-1) === '/')
1215 this.host = this.host.substring(0, this.host.length - 1);
1217 // string - the grouping method requested by the user
1218 this.method = self.data('method') || NETDATA.chartDefaults.method;
1220 // the time-range requested by the user
1221 this.after = self.data('after') || NETDATA.chartDefaults.after;
1222 this.before = self.data('before') || NETDATA.chartDefaults.before;
1224 // the pixels per point requested by the user
1225 this.pixels_per_point = self.data('pixels-per-point') || 1;
1226 this.points = self.data('points') || null;
1228 // the dimensions requested by the user
1229 this.dimensions = self.data('dimensions') || null;
1231 // the chart library requested by the user
1232 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1234 // object - the chart library used
1235 this.library = null;
1239 this.colors_assigned = {};
1240 this.colors_available = null;
1242 // the element already created by the user
1243 this.element_message = null;
1245 // the element with the chart
1246 this.element_chart = null;
1248 // the element with the legend of the chart (if created by us)
1249 this.element_legend = null;
1250 this.element_legend_childs = {
1260 this.chart_url = null; // string - the url to download chart info
1261 this.chart = null; // object - the chart as downloaded from the server
1263 this.title = self.data('title') || null; // the title of the chart
1264 this.units = self.data('units') || null; // the units of the chart dimensions
1265 this.append_options = self.data('append-options') || null; // 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 = Date.now();
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 = Date.now();
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 = Date.now();
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 = Date.now();
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 = Date.now();
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 = Date.now();
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 = Date.now();
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 = Date.now();
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 = Date.now();
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 = Date.now() + ms;
1844 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + 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 > Date.now())
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 = Date.now() + 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-' + name.toLowerCase() + '-at') || null;
2424 if(user_id === null)
2425 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2426 if(user_id !== null) {
2427 user_element = document.getElementById(user_id) || null;
2428 if (user_element === null)
2429 state.log('Cannot find element with id: ' + user_id);
2432 state.element_legend_childs.series[name] = {
2433 name: document.createElement('span'),
2434 value: document.createElement('span'),
2439 var label = state.element_legend_childs.series[name];
2441 // create the dimension visibility tracking for this label
2442 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2444 var rgb = NETDATA.colorHex2Rgb(color);
2445 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2446 + state.chart.chart_type
2447 + '" style="background-color: '
2448 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2449 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2451 var text = document.createTextNode(' ' + name);
2452 label.name.appendChild(text);
2455 parent.appendChild(document.createElement('br'));
2457 parent.appendChild(label.name);
2458 parent.appendChild(label.value);
2461 var content = document.createElement('div');
2463 if(this.hasLegend()) {
2464 this.element_legend_childs = {
2466 resize_handler: document.createElement('div'),
2467 toolbox: document.createElement('div'),
2468 toolbox_left: document.createElement('div'),
2469 toolbox_right: document.createElement('div'),
2470 toolbox_reset: document.createElement('div'),
2471 toolbox_zoomin: document.createElement('div'),
2472 toolbox_zoomout: document.createElement('div'),
2473 toolbox_volume: document.createElement('div'),
2474 title_date: document.createElement('span'),
2475 title_time: document.createElement('span'),
2476 title_units: document.createElement('span'),
2477 nano: document.createElement('div'),
2479 paneClass: 'netdata-legend-series-pane',
2480 sliderClass: 'netdata-legend-series-slider',
2481 contentClass: 'netdata-legend-series-content',
2482 enabledClass: '__enabled',
2483 flashedClass: '__flashed',
2484 activeClass: '__active',
2486 alwaysVisible: true,
2492 this.element_legend.innerHTML = '';
2494 if(this.library.toolboxPanAndZoom !== null) {
2496 function get_pan_and_zoom_step(event) {
2498 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2500 else if (event.shiftKey)
2501 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2503 else if (event.altKey)
2504 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2507 return NETDATA.options.current.pan_and_zoom_factor;
2510 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2511 this.element.appendChild(this.element_legend_childs.toolbox);
2513 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2514 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2515 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2516 this.element_legend_childs.toolbox_left.onclick = function(e) {
2519 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2520 var before = that.view_before - step;
2521 var after = that.view_after - step;
2522 if(after >= that.netdata_first)
2523 that.library.toolboxPanAndZoom(that, after, before);
2525 if(NETDATA.options.current.show_help === true)
2526 $(this.element_legend_childs.toolbox_left).popover({
2531 placement: 'bottom',
2532 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2534 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>'
2538 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2539 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2540 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2541 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2543 NETDATA.resetAllCharts(that);
2545 if(NETDATA.options.current.show_help === true)
2546 $(this.element_legend_childs.toolbox_reset).popover({
2551 placement: 'bottom',
2552 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2553 title: 'Chart Reset',
2554 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>'
2557 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2558 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2559 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2560 this.element_legend_childs.toolbox_right.onclick = function(e) {
2562 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2563 var before = that.view_before + step;
2564 var after = that.view_after + step;
2565 if(before <= that.netdata_last)
2566 that.library.toolboxPanAndZoom(that, after, before);
2568 if(NETDATA.options.current.show_help === true)
2569 $(this.element_legend_childs.toolbox_right).popover({
2574 placement: 'bottom',
2575 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2577 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>'
2581 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2582 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2583 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2584 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2586 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2587 var before = that.view_before - dt;
2588 var after = that.view_after + dt;
2589 that.library.toolboxPanAndZoom(that, after, before);
2591 if(NETDATA.options.current.show_help === true)
2592 $(this.element_legend_childs.toolbox_zoomin).popover({
2597 placement: 'bottom',
2598 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2599 title: 'Chart Zoom In',
2600 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>'
2603 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2604 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2605 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2606 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2608 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);
2609 var before = that.view_before + dt;
2610 var after = that.view_after - dt;
2612 that.library.toolboxPanAndZoom(that, after, before);
2614 if(NETDATA.options.current.show_help === true)
2615 $(this.element_legend_childs.toolbox_zoomout).popover({
2620 placement: 'bottom',
2621 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2622 title: 'Chart Zoom Out',
2623 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>'
2626 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2627 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2628 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2629 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2630 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2631 //e.preventDefault();
2632 //alert('clicked toolbox_volume on ' + that.id);
2636 this.element_legend_childs.toolbox = null;
2637 this.element_legend_childs.toolbox_left = null;
2638 this.element_legend_childs.toolbox_reset = null;
2639 this.element_legend_childs.toolbox_right = null;
2640 this.element_legend_childs.toolbox_zoomin = null;
2641 this.element_legend_childs.toolbox_zoomout = null;
2642 this.element_legend_childs.toolbox_volume = null;
2645 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2646 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2647 this.element.appendChild(this.element_legend_childs.resize_handler);
2648 if(NETDATA.options.current.show_help === true)
2649 $(this.element_legend_childs.resize_handler).popover({
2654 placement: 'bottom',
2655 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2656 title: 'Chart Resize',
2657 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>'
2661 this.element_legend_childs.resize_handler.onmousedown =
2663 that.resizeHandler(e);
2667 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2668 that.resizeHandler(e);
2671 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2672 this.element_legend.appendChild(this.element_legend_childs.title_date);
2674 this.element_legend.appendChild(document.createElement('br'));
2676 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2677 this.element_legend.appendChild(this.element_legend_childs.title_time);
2679 this.element_legend.appendChild(document.createElement('br'));
2681 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2682 this.element_legend.appendChild(this.element_legend_childs.title_units);
2684 this.element_legend.appendChild(document.createElement('br'));
2686 this.element_legend_childs.nano.className = 'netdata-legend-series';
2687 this.element_legend.appendChild(this.element_legend_childs.nano);
2689 content.className = 'netdata-legend-series-content';
2690 this.element_legend_childs.nano.appendChild(content);
2692 if(NETDATA.options.current.show_help === true)
2693 $(content).popover({
2698 placement: 'bottom',
2699 title: 'Chart Legend',
2700 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2701 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>'
2705 this.element_legend_childs = {
2707 resize_handler: null,
2710 toolbox_right: null,
2711 toolbox_reset: null,
2712 toolbox_zoomin: null,
2713 toolbox_zoomout: null,
2714 toolbox_volume: null,
2725 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2726 if(this.debug === true)
2727 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2729 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2730 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2734 var tmp = new Array();
2735 for(var dim in this.chart.dimensions) {
2736 tmp.push(this.chart.dimensions[dim].name);
2737 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2739 this.element_legend_childs.series.labels_key = tmp.toString();
2740 if(this.debug === true)
2741 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2744 // create a hidden div to be used for hidding
2745 // the original legend of the chart library
2746 var el = document.createElement('div');
2747 if(this.element_legend !== null)
2748 this.element_legend.appendChild(el);
2749 el.style.display = 'none';
2751 this.element_legend_childs.hidden = document.createElement('div');
2752 el.appendChild(this.element_legend_childs.hidden);
2754 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2755 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2757 this.legendShowLatestValues();
2760 this.hasLegend = function() {
2761 if(typeof this.___hasLegendCache___ !== 'undefined')
2762 return this.___hasLegendCache___;
2765 if(this.library && this.library.legend(this) === 'right-side') {
2766 var legend = $(this.element).data('legend') || 'yes';
2767 if(legend === 'yes') leg = true;
2770 this.___hasLegendCache___ = leg;
2774 this.legendWidth = function() {
2775 return (this.hasLegend())?140:0;
2778 this.legendHeight = function() {
2779 return $(this.element).height();
2782 this.chartWidth = function() {
2783 return $(this.element).width() - this.legendWidth();
2786 this.chartHeight = function() {
2787 return $(this.element).height();
2790 this.chartPixelsPerPoint = function() {
2791 // force an options provided detail
2792 var px = this.pixels_per_point;
2794 if(this.library && px < this.library.pixels_per_point(this))
2795 px = this.library.pixels_per_point(this);
2797 if(px < NETDATA.options.current.pixels_per_point)
2798 px = NETDATA.options.current.pixels_per_point;
2803 this.needsRecreation = function() {
2805 this.chart_created === true
2807 && this.library.autoresize() === false
2808 && this.tm.last_resized < NETDATA.options.last_resized
2812 this.chartURL = function() {
2813 var after, before, points_multiplier = 1;
2814 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2815 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2817 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2818 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2819 this.view_after = after * 1000;
2820 this.view_before = before * 1000;
2822 this.requested_padding = null;
2823 points_multiplier = 1;
2825 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2826 this.tm.pan_and_zoom_seq = 0;
2828 before = Math.round(this.current.force_before_ms / 1000);
2829 after = Math.round(this.current.force_after_ms / 1000);
2830 this.view_after = after * 1000;
2831 this.view_before = before * 1000;
2833 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2834 this.requested_padding = Math.round((before - after) / 2);
2835 after -= this.requested_padding;
2836 before += this.requested_padding;
2837 this.requested_padding *= 1000;
2838 points_multiplier = 2;
2841 this.current.force_before_ms = null;
2842 this.current.force_after_ms = null;
2845 this.tm.pan_and_zoom_seq = 0;
2847 before = this.before;
2849 this.view_after = after * 1000;
2850 this.view_before = before * 1000;
2852 this.requested_padding = null;
2853 points_multiplier = 1;
2856 this.requested_after = after * 1000;
2857 this.requested_before = before * 1000;
2859 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2861 // build the data URL
2862 this.data_url = this.host + this.chart.data_url;
2863 this.data_url += "&format=" + this.library.format();
2864 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2865 this.data_url += "&group=" + this.method;
2866 this.data_url += "&options=" + this.library.options(this);
2867 this.data_url += '|jsonwrap';
2869 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2870 this.data_url += '|nonzero';
2872 if(this.append_options !== null)
2873 this.data_url += '|' + this.append_options.toString();
2876 this.data_url += "&after=" + after.toString();
2879 this.data_url += "&before=" + before.toString();
2882 this.data_url += "&dimensions=" + this.dimensions;
2884 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2885 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2888 this.redrawChart = function() {
2889 if(this.data !== null)
2890 this.updateChartWithData(this.data);
2893 this.updateChartWithData = function(data) {
2894 if(this.debug === true)
2895 this.log('updateChartWithData() called.');
2897 // this may force the chart to be re-created
2901 this.updates_counter++;
2902 this.updates_since_last_unhide++;
2903 this.updates_since_last_creation++;
2905 var started = Date.now();
2907 // if the result is JSON, find the latest update-every
2908 this.data_update_every = data.view_update_every * 1000;
2909 this.data_after = data.after * 1000;
2910 this.data_before = data.before * 1000;
2911 this.netdata_first = data.first_entry * 1000;
2912 this.netdata_last = data.last_entry * 1000;
2913 this.data_points = data.points;
2916 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2917 if(this.view_after < this.data_after) {
2918 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2919 this.view_after = this.data_after;
2922 if(this.view_before > this.data_before) {
2923 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2924 this.view_before = this.data_before;
2928 this.view_after = this.data_after;
2929 this.view_before = this.data_before;
2932 if(this.debug === true) {
2933 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2935 if(this.current.force_after_ms)
2936 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2938 this.log('STATUS: forced : unset');
2940 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2941 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2942 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2943 this.log('STATUS: points : ' + (this.data_points).toString());
2946 if(this.data_points === 0) {
2951 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2952 if(this.debug === true)
2953 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2955 this.chart_created = false;
2958 // check and update the legend
2959 this.legendUpdateDOM();
2961 if(this.chart_created === true
2962 && typeof this.library.update === 'function') {
2964 if(this.debug === true)
2965 this.log('updating chart...');
2967 if(callChartLibraryUpdateSafely(data) === false)
2971 if(this.debug === true)
2972 this.log('creating chart...');
2974 if(callChartLibraryCreateSafely(data) === false)
2978 this.legendShowLatestValues();
2979 if(this.selected === true)
2980 NETDATA.globalSelectionSync.stop();
2982 // update the performance counters
2983 var now = Date.now();
2984 this.tm.last_updated = now;
2986 // don't update last_autorefreshed if this chart is
2987 // forced to be updated with global PanAndZoom
2988 if(NETDATA.globalPanAndZoom.isActive())
2989 this.tm.last_autorefreshed = 0;
2991 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
2992 this.tm.last_autorefreshed = now - (now % this.data_update_every);
2994 this.tm.last_autorefreshed = now;
2997 this.refresh_dt_ms = now - started;
2998 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3000 if(this.refresh_dt_element !== null)
3001 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
3004 this.updateChart = function(callback) {
3005 if(this.debug === true)
3006 this.log('updateChart() called.');
3008 if(this._updating === true) {
3009 if(this.debug === true)
3010 this.log('I am already updating...');
3012 if(typeof callback === 'function') callback();
3016 // due to late initialization of charts and libraries
3017 // we need to check this too
3018 if(this.enabled === false) {
3019 if(this.debug === true)
3020 this.log('I am not enabled');
3022 if(typeof callback === 'function') callback();
3026 if(canBeRendered() === false) {
3027 if(typeof callback === 'function') callback();
3031 if(this.chart === null) {
3032 this.getChart(function() { that.updateChart(callback); });
3036 if(this.library.initialized === false) {
3037 if(this.library.enabled === true) {
3038 this.library.initialize(function() { that.updateChart(callback); });
3042 error('chart library "' + this.library_name + '" is not available.');
3043 if(typeof callback === 'function') callback();
3048 this.clearSelection();
3051 if(this.debug === true)
3052 this.log('updating from ' + this.data_url);
3054 NETDATA.statistics.refreshes_total++;
3055 NETDATA.statistics.refreshes_active++;
3057 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3058 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3060 this._updating = true;
3062 this.xhr = $.ajax( {
3067 'Cache-Control': 'no-cache, no-store',
3068 'Pragma': 'no-cache'
3070 xhrFields: { withCredentials: true } // required for the cookie
3072 .done(function(data) {
3073 that.xhr = undefined;
3075 if(that.debug === true)
3076 that.log('data received. updating chart.');
3078 that.updateChartWithData(data);
3080 .fail(function(msg) {
3081 that.xhr = undefined;
3083 if(msg.statusText !== 'abort')
3084 error('data download failed for url: ' + that.data_url);
3086 .always(function() {
3087 that.xhr = undefined;
3089 NETDATA.statistics.refreshes_active--;
3090 that._updating = false;
3091 if(typeof callback === 'function') callback();
3097 this.isVisible = function(nocache) {
3098 if(typeof nocache === 'undefined')
3101 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3103 // caching - we do not evaluate the charts visibility
3104 // if the page has not been scrolled since the last check
3105 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3106 return this.___isVisible___;
3108 this.tm.last_visible_check = Date.now();
3110 var wh = window.innerHeight;
3111 var x = this.element.getBoundingClientRect();
3115 if(x.width === 0 || x.height === 0) {
3117 this.___isVisible___ = false;
3118 return this.___isVisible___;
3121 if(x.top < 0 && -x.top > x.height) {
3122 // the chart is entirely above
3123 ret = -x.top - x.height;
3125 else if(x.top > wh) {
3126 // the chart is entirely below
3130 if(ret > tolerance) {
3131 // the chart is too far
3134 this.___isVisible___ = false;
3135 return this.___isVisible___;
3138 // the chart is inside or very close
3141 this.___isVisible___ = true;
3142 return this.___isVisible___;
3146 this.isAutoRefreshable = function() {
3147 return (this.current.autorefresh);
3150 this.canBeAutoRefreshed = function() {
3151 var now = Date.now();
3153 if(this.running === true) {
3154 if(this.debug === true)
3155 this.log('I am already running');
3160 if(this.enabled === false) {
3161 if(this.debug === true)
3162 this.log('I am not enabled');
3167 if(this.library === null || this.library.enabled === false) {
3168 error('charting library "' + this.library_name + '" is not available');
3169 if(this.debug === true)
3170 this.log('My chart library ' + this.library_name + ' is not available');
3175 if(this.isVisible() === false) {
3176 if(NETDATA.options.debug.visibility === true || this.debug === true)
3177 this.log('I am not visible');
3182 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3183 if(this.debug === true)
3184 this.log('timed force update detected - allowing this update');
3186 this.current.force_update_at = 0;
3190 if(this.isAutoRefreshable() === true) {
3191 // allow the first update, even if the page is not visible
3192 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3193 if(NETDATA.options.debug.focus === true || this.debug === true)
3194 this.log('canBeAutoRefreshed(): page does not have focus');
3199 if(this.needsRecreation() === true) {
3200 if(this.debug === true)
3201 this.log('canBeAutoRefreshed(): needs re-creation.');
3206 // options valid only for autoRefresh()
3207 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3208 if(NETDATA.globalPanAndZoom.isActive()) {
3209 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3210 if(this.debug === true)
3211 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3216 if(this.debug === true)
3217 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3223 if(this.selected === true) {
3224 if(this.debug === true)
3225 this.log('canBeAutoRefreshed(): I have a selection in place.');
3230 if(this.paused === true) {
3231 if(this.debug === true)
3232 this.log('canBeAutoRefreshed(): I am paused.');
3237 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3238 if(this.debug === true)
3239 this.log('canBeAutoRefreshed(): It is time to update me.');
3249 this.autoRefresh = function(callback) {
3250 if(this.canBeAutoRefreshed() === true && this.running === false) {
3253 state.running = true;
3254 state.updateChart(function() {
3255 state.running = false;
3257 if(typeof callback !== 'undefined')
3262 if(typeof callback !== 'undefined')
3267 this._defaultsFromDownloadedChart = function(chart) {
3269 this.chart_url = chart.url;
3270 this.data_update_every = chart.update_every * 1000;
3271 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3272 this.tm.last_info_downloaded = Date.now();
3274 if(this.title === null)
3275 this.title = chart.title;
3277 if(this.units === null)
3278 this.units = chart.units;
3281 // fetch the chart description from the netdata server
3282 this.getChart = function(callback) {
3283 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3285 this._defaultsFromDownloadedChart(this.chart);
3286 if(typeof callback === 'function') callback();
3289 this.chart_url = "/api/v1/chart?chart=" + this.id;
3291 if(this.debug === true)
3292 this.log('downloading ' + this.chart_url);
3295 url: this.host + this.chart_url,
3298 xhrFields: { withCredentials: true } // required for the cookie
3300 .done(function(chart) {
3301 chart.url = that.chart_url;
3302 that._defaultsFromDownloadedChart(chart);
3303 NETDATA.chartRegistry.add(that.host, that.id, chart);
3306 NETDATA.error(404, that.chart_url);
3307 error('chart not found on url "' + that.chart_url + '"');
3309 .always(function() {
3310 if(typeof callback === 'function') callback();
3315 // ============================================================================================================
3321 NETDATA.resetAllCharts = function(state) {
3322 // first clear the global selection sync
3323 // to make sure no chart is in selected state
3324 state.globalSelectionSyncStop();
3326 // there are 2 possibilities here
3327 // a. state is the global Pan and Zoom master
3328 // b. state is not the global Pan and Zoom master
3330 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3333 // clear the global Pan and Zoom
3334 // this will also refresh the master
3335 // and unblock any charts currently mirroring the master
3336 NETDATA.globalPanAndZoom.clearMaster();
3338 // if we were not the master, reset our status too
3339 // this is required because most probably the mouse
3340 // is over this chart, blocking it from auto-refreshing
3341 if(master === false && (state.paused === true || state.selected === true))
3345 // get or create a chart state, given a DOM element
3346 NETDATA.chartState = function(element) {
3347 var state = $(element).data('netdata-state-object') || null;
3348 if(state === null) {
3349 state = new chartState(element);
3350 $(element).data('netdata-state-object', state);
3355 // ----------------------------------------------------------------------------------------------------------------
3356 // Library functions
3358 // Load a script without jquery
3359 // This is used to load jquery - after it is loaded, we use jquery
3360 NETDATA._loadjQuery = function(callback) {
3361 if(typeof jQuery === 'undefined') {
3362 if(NETDATA.options.debug.main_loop === true)
3363 console.log('loading ' + NETDATA.jQuery);
3365 var script = document.createElement('script');
3366 script.type = 'text/javascript';
3367 script.async = true;
3368 script.src = NETDATA.jQuery;
3370 // script.onabort = onError;
3371 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3372 if(typeof callback === "function")
3373 script.onload = callback;
3375 var s = document.getElementsByTagName('script')[0];
3376 s.parentNode.insertBefore(script, s);
3378 else if(typeof callback === "function")
3382 NETDATA._loadCSS = function(filename) {
3383 // don't use jQuery here
3384 // styles are loaded before jQuery
3385 // to eliminate showing an unstyled page to the user
3387 var fileref = document.createElement("link");
3388 fileref.setAttribute("rel", "stylesheet");
3389 fileref.setAttribute("type", "text/css");
3390 fileref.setAttribute("href", filename);
3392 if (typeof fileref !== 'undefined')
3393 document.getElementsByTagName("head")[0].appendChild(fileref);
3396 NETDATA.colorHex2Rgb = function(hex) {
3397 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3398 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3399 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3400 return r + r + g + g + b + b;
3403 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3405 r: parseInt(result[1], 16),
3406 g: parseInt(result[2], 16),
3407 b: parseInt(result[3], 16)
3411 NETDATA.colorLuminance = function(hex, lum) {
3412 // validate hex string
3413 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3415 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3419 // convert to decimal and change luminosity
3420 var rgb = "#", c, i;
3421 for (i = 0; i < 3; i++) {
3422 c = parseInt(hex.substr(i*2,2), 16);
3423 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3424 rgb += ("00"+c).substr(c.length);
3430 NETDATA.guid = function() {
3432 return Math.floor((1 + Math.random()) * 0x10000)
3437 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3440 NETDATA.zeropad = function(x) {
3441 if(x > -10 && x < 10) return '0' + x.toString();
3442 else return x.toString();
3445 // user function to signal us the DOM has been
3447 NETDATA.updatedDom = function() {
3448 NETDATA.options.updated_dom = true;
3451 NETDATA.ready = function(callback) {
3452 NETDATA.options.pauseCallback = callback;
3455 NETDATA.pause = function(callback) {
3456 if(NETDATA.options.pause === true)
3459 NETDATA.options.pauseCallback = callback;
3462 NETDATA.unpause = function() {
3463 NETDATA.options.pauseCallback = null;
3464 NETDATA.options.updated_dom = true;
3465 NETDATA.options.pause = false;
3468 // ----------------------------------------------------------------------------------------------------------------
3470 // this is purely sequencial charts refresher
3471 // it is meant to be autonomous
3472 NETDATA.chartRefresherNoParallel = function(index) {
3473 if(NETDATA.options.debug.mail_loop === true)
3474 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3476 if(NETDATA.options.updated_dom === true) {
3477 // the dom has been updated
3478 // get the dom parts again
3479 NETDATA.parseDom(NETDATA.chartRefresher);
3482 if(index >= NETDATA.options.targets.length) {
3483 if(NETDATA.options.debug.main_loop === true)
3484 console.log('waiting to restart main loop...');
3486 NETDATA.options.auto_refresher_fast_weight = 0;
3488 setTimeout(function() {
3489 NETDATA.chartRefresher();
3490 }, NETDATA.options.current.idle_between_loops);
3493 var state = NETDATA.options.targets[index];
3495 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3496 if(NETDATA.options.debug.main_loop === true)
3497 console.log('fast rendering...');
3499 state.autoRefresh(function() {
3500 NETDATA.chartRefresherNoParallel(++index);
3504 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3505 NETDATA.options.auto_refresher_fast_weight = 0;
3507 setTimeout(function() {
3508 state.autoRefresh(function() {
3509 NETDATA.chartRefresherNoParallel(++index);
3511 }, NETDATA.options.current.idle_between_charts);
3516 // this is part of the parallel refresher
3517 // its cause is to refresh sequencially all the charts
3518 // that depend on chart library initialization
3519 // it will call the parallel refresher back
3520 // as soon as it sees a chart that its chart library
3522 NETDATA.chartRefresher_uninitialized = function() {
3523 if(NETDATA.options.updated_dom === true) {
3524 // the dom has been updated
3525 // get the dom parts again
3526 NETDATA.parseDom(NETDATA.chartRefresher);
3530 if(NETDATA.options.sequencial.length === 0)
3531 NETDATA.chartRefresher();
3533 var state = NETDATA.options.sequencial.pop();
3534 if(state.library.initialized === true)
3535 NETDATA.chartRefresher();
3537 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3541 NETDATA.chartRefresherWaitTime = function() {
3542 return NETDATA.options.current.idle_parallel_loops;
3545 // the default refresher
3546 // it will create 2 sets of charts:
3547 // - the ones that can be refreshed in parallel
3548 // - the ones that depend on something else
3549 // the first set will be executed in parallel
3550 // the second will be given to NETDATA.chartRefresher_uninitialized()
3551 NETDATA.chartRefresher = function() {
3552 // console.log('auto-refresher...');
3554 if(NETDATA.options.pause === true) {
3555 // console.log('auto-refresher is paused');
3556 setTimeout(NETDATA.chartRefresher,
3557 NETDATA.chartRefresherWaitTime());
3561 if(typeof NETDATA.options.pauseCallback === 'function') {
3562 // console.log('auto-refresher is calling pauseCallback');
3563 NETDATA.options.pause = true;
3564 NETDATA.options.pauseCallback();
3565 NETDATA.chartRefresher();
3569 if(NETDATA.options.current.parallel_refresher === false) {
3570 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3571 NETDATA.chartRefresherNoParallel(0);
3575 if(NETDATA.options.updated_dom === true) {
3576 // the dom has been updated
3577 // get the dom parts again
3578 // console.log('auto-refresher is calling parseDom()');
3579 NETDATA.parseDom(NETDATA.chartRefresher);
3583 var parallel = new Array();
3584 var targets = NETDATA.options.targets;
3585 var len = targets.length;
3588 state = targets[len];
3589 if(state.isVisible() === false || state.running === true)
3592 if(state.library.initialized === false) {
3593 if(state.library.enabled === true) {
3594 state.library.initialize(NETDATA.chartRefresher);
3598 state.error('chart library "' + state.library_name + '" is not enabled.');
3602 parallel.unshift(state);
3605 if(parallel.length > 0) {
3606 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3607 // this will execute the jobs in parallel
3608 $(parallel).each(function() {
3613 // console.log('auto-refresher nothing to do');
3616 // run the next refresh iteration
3617 setTimeout(NETDATA.chartRefresher,
3618 NETDATA.chartRefresherWaitTime());
3621 NETDATA.parseDom = function(callback) {
3622 NETDATA.options.last_page_scroll = Date.now();
3623 NETDATA.options.updated_dom = false;
3625 var targets = $('div[data-netdata]'); //.filter(':visible');
3627 if(NETDATA.options.debug.main_loop === true)
3628 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3630 NETDATA.options.targets = new Array();
3631 var len = targets.length;
3633 // the initialization will take care of sizing
3634 // and the "loading..." message
3635 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3638 if(typeof callback === 'function') callback();
3641 // this is the main function - where everything starts
3642 NETDATA.start = function() {
3643 // this should be called only once
3645 NETDATA.options.page_is_visible = true;
3647 $(window).blur(function() {
3648 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3649 NETDATA.options.page_is_visible = false;
3650 if(NETDATA.options.debug.focus === true)
3651 console.log('Lost Focus!');
3655 $(window).focus(function() {
3656 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3657 NETDATA.options.page_is_visible = true;
3658 if(NETDATA.options.debug.focus === true)
3659 console.log('Focus restored!');
3663 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3664 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3665 NETDATA.options.page_is_visible = false;
3666 if(NETDATA.options.debug.focus === true)
3667 console.log('Document has no focus!');
3671 // bootstrap tab switching
3672 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3674 // bootstrap modal switching
3675 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3676 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3678 // bootstrap collapse switching
3679 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3680 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3682 NETDATA.parseDom(NETDATA.chartRefresher);
3684 // Alarms initialization
3685 setTimeout(NETDATA.alarms.init, 1000);
3687 // Registry initialization
3688 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3690 if(typeof netdataCallback === 'function')
3694 // ----------------------------------------------------------------------------------------------------------------
3697 NETDATA.peityInitialize = function(callback) {
3698 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3700 url: NETDATA.peity_js,
3703 xhrFields: { withCredentials: true } // required for the cookie
3706 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3709 NETDATA.chartLibraries.peity.enabled = false;
3710 NETDATA.error(100, NETDATA.peity_js);
3712 .always(function() {
3713 if(typeof callback === "function")
3718 NETDATA.chartLibraries.peity.enabled = false;
3719 if(typeof callback === "function")
3724 NETDATA.peityChartUpdate = function(state, data) {
3725 state.peity_instance.innerHTML = data.result;
3727 if(state.peity_options.stroke !== state.chartColors()[0]) {
3728 state.peity_options.stroke = state.chartColors()[0];
3729 if(state.chart.chart_type === 'line')
3730 state.peity_options.fill = NETDATA.themes.current.background;
3732 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3735 $(state.peity_instance).peity('line', state.peity_options);
3739 NETDATA.peityChartCreate = function(state, data) {
3740 state.peity_instance = document.createElement('div');
3741 state.element_chart.appendChild(state.peity_instance);
3743 var self = $(state.element);
3744 state.peity_options = {
3745 stroke: NETDATA.themes.current.foreground,
3746 strokeWidth: self.data('peity-strokewidth') || 1,
3747 width: state.chartWidth(),
3748 height: state.chartHeight(),
3749 fill: NETDATA.themes.current.foreground
3752 NETDATA.peityChartUpdate(state, data);
3756 // ----------------------------------------------------------------------------------------------------------------
3759 NETDATA.sparklineInitialize = function(callback) {
3760 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3762 url: NETDATA.sparkline_js,
3765 xhrFields: { withCredentials: true } // required for the cookie
3768 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3771 NETDATA.chartLibraries.sparkline.enabled = false;
3772 NETDATA.error(100, NETDATA.sparkline_js);
3774 .always(function() {
3775 if(typeof callback === "function")
3780 NETDATA.chartLibraries.sparkline.enabled = false;
3781 if(typeof callback === "function")
3786 NETDATA.sparklineChartUpdate = function(state, data) {
3787 state.sparkline_options.width = state.chartWidth();
3788 state.sparkline_options.height = state.chartHeight();
3790 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3794 NETDATA.sparklineChartCreate = function(state, data) {
3795 var self = $(state.element);
3796 var type = self.data('sparkline-type') || 'line';
3797 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3798 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3799 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3800 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3801 var composite = self.data('sparkline-composite') || undefined;
3802 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3803 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3804 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3805 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3806 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3807 var spotColor = self.data('sparkline-spotcolor') || undefined;
3808 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3809 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3810 var spotRadius = self.data('sparkline-spotradius') || undefined;
3811 var valueSpots = self.data('sparkline-valuespots') || undefined;
3812 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3813 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3814 var lineWidth = self.data('sparkline-linewidth') || undefined;
3815 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3816 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3817 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3818 var xvalues = self.data('sparkline-xvalues') || undefined;
3819 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3820 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3821 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3822 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3823 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3824 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3825 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3826 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3827 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3828 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3829 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3830 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3831 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3832 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3833 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3834 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3835 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3836 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3837 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3838 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3839 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3840 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3842 if(spotColor === 'disable') spotColor='';
3843 if(minSpotColor === 'disable') minSpotColor='';
3844 if(maxSpotColor === 'disable') maxSpotColor='';
3846 state.sparkline_options = {
3848 lineColor: lineColor,
3849 fillColor: fillColor,
3850 chartRangeMin: chartRangeMin,
3851 chartRangeMax: chartRangeMax,
3852 composite: composite,
3853 enableTagOptions: enableTagOptions,
3854 tagOptionPrefix: tagOptionPrefix,
3855 tagValuesAttribute: tagValuesAttribute,
3856 disableHiddenCheck: disableHiddenCheck,
3857 defaultPixelsPerValue: defaultPixelsPerValue,
3858 spotColor: spotColor,
3859 minSpotColor: minSpotColor,
3860 maxSpotColor: maxSpotColor,
3861 spotRadius: spotRadius,
3862 valueSpots: valueSpots,
3863 highlightSpotColor: highlightSpotColor,
3864 highlightLineColor: highlightLineColor,
3865 lineWidth: lineWidth,
3866 normalRangeMin: normalRangeMin,
3867 normalRangeMax: normalRangeMax,
3868 drawNormalOnTop: drawNormalOnTop,
3870 chartRangeClip: chartRangeClip,
3871 chartRangeMinX: chartRangeMinX,
3872 chartRangeMaxX: chartRangeMaxX,
3873 disableInteraction: disableInteraction,
3874 disableTooltips: disableTooltips,
3875 disableHighlight: disableHighlight,
3876 highlightLighten: highlightLighten,
3877 highlightColor: highlightColor,
3878 tooltipContainer: tooltipContainer,
3879 tooltipClassname: tooltipClassname,
3880 tooltipChartTitle: state.title,
3881 tooltipFormat: tooltipFormat,
3882 tooltipPrefix: tooltipPrefix,
3883 tooltipSuffix: tooltipSuffix,
3884 tooltipSkipNull: tooltipSkipNull,
3885 tooltipValueLookups: tooltipValueLookups,
3886 tooltipFormatFieldlist: tooltipFormatFieldlist,
3887 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3888 numberFormatter: numberFormatter,
3889 numberDigitGroupSep: numberDigitGroupSep,
3890 numberDecimalMark: numberDecimalMark,
3891 numberDigitGroupCount: numberDigitGroupCount,
3892 animatedZooms: animatedZooms,
3893 width: state.chartWidth(),
3894 height: state.chartHeight()
3897 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3901 // ----------------------------------------------------------------------------------------------------------------
3908 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3909 if(after < state.netdata_first)
3910 after = state.netdata_first;
3912 if(before > state.netdata_last)
3913 before = state.netdata_last;
3915 state.setMode('zoom');
3916 state.globalSelectionSyncStop();
3917 state.globalSelectionSyncDelay();
3918 state.dygraph_user_action = true;
3919 state.dygraph_force_zoom = true;
3920 state.updateChartPanOrZoom(after, before);
3921 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3924 NETDATA.dygraphSetSelection = function(state, t) {
3925 if(typeof state.dygraph_instance !== 'undefined') {
3926 var r = state.calculateRowForTime(t);
3928 state.dygraph_instance.setSelection(r);
3930 state.dygraph_instance.clearSelection();
3931 state.legendShowUndefined();
3938 NETDATA.dygraphClearSelection = function(state, t) {
3939 if(typeof state.dygraph_instance !== 'undefined') {
3940 state.dygraph_instance.clearSelection();
3945 NETDATA.dygraphSmoothInitialize = function(callback) {
3947 url: NETDATA.dygraph_smooth_js,
3950 xhrFields: { withCredentials: true } // required for the cookie
3953 NETDATA.dygraph.smooth = true;
3954 smoothPlotter.smoothing = 0.3;
3957 NETDATA.dygraph.smooth = false;
3959 .always(function() {
3960 if(typeof callback === "function")
3965 NETDATA.dygraphInitialize = function(callback) {
3966 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3968 url: NETDATA.dygraph_js,
3971 xhrFields: { withCredentials: true } // required for the cookie
3974 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3977 NETDATA.chartLibraries.dygraph.enabled = false;
3978 NETDATA.error(100, NETDATA.dygraph_js);
3980 .always(function() {
3981 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
3982 NETDATA.dygraphSmoothInitialize(callback);
3983 else if(typeof callback === "function")
3988 NETDATA.chartLibraries.dygraph.enabled = false;
3989 if(typeof callback === "function")
3994 NETDATA.dygraphChartUpdate = function(state, data) {
3995 var dygraph = state.dygraph_instance;
3997 if(typeof dygraph === 'undefined')
3998 return NETDATA.dygraphChartCreate(state, data);
4000 // when the chart is not visible, and hidden
4001 // if there is a window resize, dygraph detects
4002 // its element size as 0x0.
4003 // this will make it re-appear properly
4005 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4009 file: data.result.data,
4010 colors: state.chartColors(),
4011 labels: data.result.labels,
4012 labelsDivWidth: state.chartWidth() - 70,
4013 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4016 if(state.dygraph_force_zoom === true) {
4017 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4018 state.log('dygraphChartUpdate() forced zoom update');
4020 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4021 options.isZoomedIgnoreProgrammaticZoom = true;
4022 state.dygraph_force_zoom = false;
4024 else if(state.current.name !== 'auto') {
4025 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4026 state.log('dygraphChartUpdate() loose update');
4029 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4030 state.log('dygraphChartUpdate() strict update');
4032 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4033 options.isZoomedIgnoreProgrammaticZoom = true;
4036 options.valueRange = state.dygraph_options.valueRange;
4038 var oldMax = null, oldMin = null;
4039 if(state.__commonMin !== null) {
4040 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4041 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4043 if(state.__commonMax !== null) {
4044 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4045 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4048 if(state.dygraph_smooth_eligible === true) {
4049 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4050 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4051 NETDATA.dygraphChartCreate(state, data);
4056 dygraph.updateOptions(options);
4059 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4060 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4061 options.valueRange[0] = NETDATA.commonMin.get(state);
4064 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4065 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4066 options.valueRange[1] = NETDATA.commonMax.get(state);
4070 if(redraw === true) {
4071 // state.log('forcing redraw to adapt to common- min/max');
4072 dygraph.updateOptions(options);
4075 state.dygraph_last_rendered = Date.now();
4079 NETDATA.dygraphChartCreate = function(state, data) {
4080 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4081 state.log('dygraphChartCreate()');
4083 var self = $(state.element);
4085 var chart_type = state.chart.chart_type;
4086 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4087 chart_type = self.data('dygraph-type') || chart_type;
4089 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
4090 smooth = self.data('dygraph-smooth') || smooth;
4092 if(NETDATA.dygraph.smooth === false)
4095 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
4096 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
4098 state.dygraph_options = {
4099 colors: self.data('dygraph-colors') || state.chartColors(),
4101 // leave a few pixels empty on the right of the chart
4102 rightGap: self.data('dygraph-rightgap') || 5,
4103 showRangeSelector: self.data('dygraph-showrangeselector') || false,
4104 showRoller: self.data('dygraph-showroller') || false,
4106 title: self.data('dygraph-title') || state.title,
4107 titleHeight: self.data('dygraph-titleheight') || 19,
4109 legend: self.data('dygraph-legend') || 'always', // we need this to get selection events
4110 labels: data.result.labels,
4111 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
4112 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
4113 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
4114 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
4115 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
4118 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
4119 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
4121 includeZero: self.data('dygraph-includezero') || ((chart_type === 'stacked')? true : false),
4122 xRangePad: self.data('dygraph-xrangepad') || 0,
4123 yRangePad: self.data('dygraph-yrangepad') || 1,
4125 valueRange: self.data('dygraph-valuerange') || [ null, null ],
4127 ylabel: state.units,
4128 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
4130 // the function to plot the chart
4133 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
4134 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
4135 strokePattern: self.data('dygraph-strokepattern') || undefined,
4137 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
4138 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
4139 drawPoints: self.data('dygraph-drawpoints') || false,
4141 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
4142 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
4144 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
4145 pointSize: self.data('dygraph-pointsize') || 1,
4147 // enabling this makes the chart with little square lines
4148 stepPlot: self.data('dygraph-stepplot') || false,
4150 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
4151 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
4152 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
4154 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
4155 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
4156 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
4157 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
4159 drawAxis: self.data('dygraph-drawaxis') || true,
4160 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
4161 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
4162 axisLineWidth: self.data('dygraph-axislinewidth') || 1.0,
4164 drawGrid: self.data('dygraph-drawgrid') || true,
4165 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4166 gridLineWidth: self.data('dygraph-gridlinewidth') || 1.0,
4167 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4169 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4170 sigFigs: self.data('dygraph-sigfigs') || null,
4171 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4172 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4174 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4175 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4176 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4178 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4179 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4183 ticker: Dygraph.dateTicker,
4184 axisLabelFormatter: function (d, gran) {
4185 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4187 valueFormatter: function (ms) {
4188 var d = new Date(ms);
4189 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4190 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4195 valueFormatter: function (x) {
4196 // we format legends with the state object
4197 // no need to do anything here
4198 // return (Math.round(x*100) / 100).toLocaleString();
4199 // return state.legendFormatValue(x);
4204 legendFormatter: function(data) {
4205 var elements = state.element_legend_childs;
4207 // if the hidden div is not there
4208 // we are not managing the legend
4209 if(elements.hidden === null) return;
4211 if (typeof data.x !== 'undefined') {
4212 state.legendSetDate(data.x);
4213 var i = data.series.length;
4215 var series = data.series[i];
4216 if(!series.isVisible) continue;
4217 state.legendSetLabelValue(series.label, series.y);
4223 drawCallback: function(dygraph, is_initial) {
4224 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4225 state.dygraph_user_action = false;
4227 var x_range = dygraph.xAxisRange();
4228 var after = Math.round(x_range[0]);
4229 var before = Math.round(x_range[1]);
4231 if(NETDATA.options.debug.dygraph === true)
4232 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4234 if(before <= state.netdata_last && after >= state.netdata_first)
4235 state.updateChartPanOrZoom(after, before);
4238 zoomCallback: function(minDate, maxDate, yRanges) {
4239 if(NETDATA.options.debug.dygraph === true)
4240 state.log('dygraphZoomCallback()');
4242 state.globalSelectionSyncStop();
4243 state.globalSelectionSyncDelay();
4244 state.setMode('zoom');
4246 // refresh it to the greatest possible zoom level
4247 state.dygraph_user_action = true;
4248 state.dygraph_force_zoom = true;
4249 state.updateChartPanOrZoom(minDate, maxDate);
4251 highlightCallback: function(event, x, points, row, seriesName) {
4252 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4253 state.log('dygraphHighlightCallback()');
4257 // there is a bug in dygraph when the chart is zoomed enough
4258 // the time it thinks is selected is wrong
4259 // here we calculate the time t based on the row number selected
4261 var t = state.data_after + row * state.data_update_every;
4262 // 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);
4264 state.globalSelectionSync(x);
4266 // fix legend zIndex using the internal structures of dygraph legend module
4267 // this works, but it is a hack!
4268 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4270 unhighlightCallback: function(event) {
4271 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4272 state.log('dygraphUnhighlightCallback()');
4274 state.unpauseChart();
4275 state.globalSelectionSyncStop();
4277 interactionModel : {
4278 mousedown: function(event, dygraph, context) {
4279 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4280 state.log('interactionModel.mousedown()');
4282 state.dygraph_user_action = true;
4283 state.globalSelectionSyncStop();
4285 if(NETDATA.options.debug.dygraph === true)
4286 state.log('dygraphMouseDown()');
4288 // Right-click should not initiate a zoom.
4289 if(event.button && event.button === 2) return;
4291 context.initializeMouseDown(event, dygraph, context);
4293 if(event.button && event.button === 1) {
4294 if (event.altKey || event.shiftKey) {
4295 state.setMode('pan');
4296 state.globalSelectionSyncDelay();
4297 Dygraph.startPan(event, dygraph, context);
4300 state.setMode('zoom');
4301 state.globalSelectionSyncDelay();
4302 Dygraph.startZoom(event, dygraph, context);
4306 if (event.altKey || event.shiftKey) {
4307 state.setMode('zoom');
4308 state.globalSelectionSyncDelay();
4309 Dygraph.startZoom(event, dygraph, context);
4312 state.setMode('pan');
4313 state.globalSelectionSyncDelay();
4314 Dygraph.startPan(event, dygraph, context);
4318 mousemove: function(event, dygraph, context) {
4319 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4320 state.log('interactionModel.mousemove()');
4322 if(context.isPanning) {
4323 state.dygraph_user_action = true;
4324 state.globalSelectionSyncStop();
4325 state.globalSelectionSyncDelay();
4326 state.setMode('pan');
4327 context.is2DPan = false;
4328 Dygraph.movePan(event, dygraph, context);
4330 else if(context.isZooming) {
4331 state.dygraph_user_action = true;
4332 state.globalSelectionSyncStop();
4333 state.globalSelectionSyncDelay();
4334 state.setMode('zoom');
4335 Dygraph.moveZoom(event, dygraph, context);
4338 mouseup: function(event, dygraph, context) {
4339 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4340 state.log('interactionModel.mouseup()');
4342 if (context.isPanning) {
4343 state.dygraph_user_action = true;
4344 state.globalSelectionSyncDelay();
4345 Dygraph.endPan(event, dygraph, context);
4347 else if (context.isZooming) {
4348 state.dygraph_user_action = true;
4349 state.globalSelectionSyncDelay();
4350 Dygraph.endZoom(event, dygraph, context);
4353 click: function(event, dygraph, context) {
4354 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4355 state.log('interactionModel.click()');
4357 event.preventDefault();
4359 dblclick: function(event, dygraph, context) {
4360 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4361 state.log('interactionModel.dblclick()');
4362 NETDATA.resetAllCharts(state);
4364 wheel: function(event, dygraph, context) {
4365 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4366 state.log('interactionModel.wheel()');
4368 // Take the offset of a mouse event on the dygraph canvas and
4369 // convert it to a pair of percentages from the bottom left.
4370 // (Not top left, bottom is where the lower value is.)
4371 function offsetToPercentage(g, offsetX, offsetY) {
4372 // This is calculating the pixel offset of the leftmost date.
4373 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4374 var yar0 = g.yAxisRange(0);
4376 // This is calculating the pixel of the higest value. (Top pixel)
4377 var yOffset = g.toDomCoords(null, yar0[1])[1];
4379 // x y w and h are relative to the corner of the drawing area,
4380 // so that the upper corner of the drawing area is (0, 0).
4381 var x = offsetX - xOffset;
4382 var y = offsetY - yOffset;
4384 // This is computing the rightmost pixel, effectively defining the
4386 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4388 // This is computing the lowest pixel, effectively defining the height.
4389 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4391 // Percentage from the left.
4392 var xPct = w === 0 ? 0 : (x / w);
4393 // Percentage from the top.
4394 var yPct = h === 0 ? 0 : (y / h);
4396 // The (1-) part below changes it from "% distance down from the top"
4397 // to "% distance up from the bottom".
4398 return [xPct, (1-yPct)];
4401 // Adjusts [x, y] toward each other by zoomInPercentage%
4402 // Split it so the left/bottom axis gets xBias/yBias of that change and
4403 // tight/top gets (1-xBias)/(1-yBias) of that change.
4405 // If a bias is missing it splits it down the middle.
4406 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4407 xBias = xBias || 0.5;
4408 yBias = yBias || 0.5;
4410 function adjustAxis(axis, zoomInPercentage, bias) {
4411 var delta = axis[1] - axis[0];
4412 var increment = delta * zoomInPercentage;
4413 var foo = [increment * bias, increment * (1-bias)];
4415 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4418 var yAxes = g.yAxisRanges();
4420 for (var i = 0; i < yAxes.length; i++) {
4421 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4424 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4427 if(event.altKey || event.shiftKey) {
4428 state.dygraph_user_action = true;
4430 state.globalSelectionSyncStop();
4431 state.globalSelectionSyncDelay();
4433 // http://dygraphs.com/gallery/interaction-api.js
4435 if(typeof event.wheelDelta === 'number' && event.wheelDelta != NaN)
4437 normal_def = event.wheelDelta / 40;
4440 normal_def = event.deltaY * -1.2;
4442 var normal = (event.detail) ? event.detail * -1 : normal_def;
4443 var percentage = normal / 50;
4445 if (!(event.offsetX && event.offsetY)){
4446 event.offsetX = event.layerX - event.target.offsetLeft;
4447 event.offsetY = event.layerY - event.target.offsetTop;
4450 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4451 var xPct = percentages[0];
4452 var yPct = percentages[1];
4454 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4455 var after = new_x_range[0];
4456 var before = new_x_range[1];
4458 var first = state.netdata_first + state.data_update_every;
4459 var last = state.netdata_last + state.data_update_every;
4462 after -= (before - last);
4469 state.setMode('zoom');
4470 if(state.updateChartPanOrZoom(after, before) === true)
4471 dygraph.updateOptions({ dateWindow: [ after, before ] });
4473 event.preventDefault();
4476 touchstart: function(event, dygraph, context) {
4477 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4478 state.log('interactionModel.touchstart()');
4480 state.dygraph_user_action = true;
4481 state.setMode('zoom');
4484 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4486 // we overwrite the touch directions at the end, to overwrite
4487 // the internal default of dygraphs
4488 context.touchDirections = { x: true, y: false };
4490 state.dygraph_last_touch_start = Date.now();
4491 state.dygraph_last_touch_move = 0;
4493 if(typeof event.touches[0].pageX === 'number')
4494 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4496 state.dygraph_last_touch_page_x = 0;
4498 touchmove: function(event, dygraph, context) {
4499 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4500 state.log('interactionModel.touchmove()');
4502 state.dygraph_user_action = true;
4503 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4505 state.dygraph_last_touch_move = Date.now();
4507 touchend: function(event, dygraph, context) {
4508 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4509 state.log('interactionModel.touchend()');
4511 state.dygraph_user_action = true;
4512 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4514 // if it didn't move, it is a selection
4515 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4516 // internal api of dygraphs
4517 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4518 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4519 if(NETDATA.dygraphSetSelection(state, t) === true)
4520 state.globalSelectionSync(t);
4523 // if it was double tap within double click time, reset the charts
4524 var now = Date.now();
4525 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4526 if(state.dygraph_last_touch_move === 0) {
4527 var dt = now - state.dygraph_last_touch_end;
4528 if(dt <= NETDATA.options.current.double_click_speed)
4529 NETDATA.resetAllCharts(state);
4533 // remember the timestamp of the last touch end
4534 state.dygraph_last_touch_end = now;
4539 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4540 state.dygraph_options.drawGrid = false;
4541 state.dygraph_options.drawAxis = false;
4542 state.dygraph_options.title = undefined;
4543 state.dygraph_options.ylabel = undefined;
4544 state.dygraph_options.yLabelWidth = 0;
4545 state.dygraph_options.labelsDivWidth = 120;
4546 state.dygraph_options.labelsDivStyles.width = '120px';
4547 state.dygraph_options.labelsSeparateLines = true;
4548 state.dygraph_options.rightGap = 0;
4549 state.dygraph_options.yRangePad = 1;
4552 if(smooth === true) {
4553 state.dygraph_smooth_eligible = true;
4555 if(NETDATA.options.current.smooth_plot === true)
4556 state.dygraph_options.plotter = smoothPlotter;
4558 else state.dygraph_smooth_eligible = false;
4560 state.dygraph_instance = new Dygraph(state.element_chart,
4561 data.result.data, state.dygraph_options);
4563 state.dygraph_force_zoom = false;
4564 state.dygraph_user_action = false;
4565 state.dygraph_last_rendered = Date.now();
4567 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4568 state.__commonMin = self.data('common-min') || null;
4569 state.__commonMax = self.data('common-max') || null;
4572 state.log('incompatible version of dygraphs detected');
4573 state.__commonMin = null;
4574 state.__commonMax = null;
4580 // ----------------------------------------------------------------------------------------------------------------
4583 NETDATA.morrisInitialize = function(callback) {
4584 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4586 // morris requires raphael
4587 if(!NETDATA.chartLibraries.raphael.initialized) {
4588 if(NETDATA.chartLibraries.raphael.enabled) {
4589 NETDATA.raphaelInitialize(function() {
4590 NETDATA.morrisInitialize(callback);
4594 NETDATA.chartLibraries.morris.enabled = false;
4595 if(typeof callback === "function")
4600 NETDATA._loadCSS(NETDATA.morris_css);
4603 url: NETDATA.morris_js,
4606 xhrFields: { withCredentials: true } // required for the cookie
4609 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4612 NETDATA.chartLibraries.morris.enabled = false;
4613 NETDATA.error(100, NETDATA.morris_js);
4615 .always(function() {
4616 if(typeof callback === "function")
4622 NETDATA.chartLibraries.morris.enabled = false;
4623 if(typeof callback === "function")
4628 NETDATA.morrisChartUpdate = function(state, data) {
4629 state.morris_instance.setData(data.result.data);
4633 NETDATA.morrisChartCreate = function(state, data) {
4635 state.morris_options = {
4636 element: state.element_chart.id,
4637 data: data.result.data,
4639 ykeys: data.dimension_names,
4640 labels: data.dimension_names,
4646 continuousLine: false,
4647 behaveLikeLine: false
4650 if(state.chart.chart_type === 'line')
4651 state.morris_instance = new Morris.Line(state.morris_options);
4653 else if(state.chart.chart_type === 'area') {
4654 state.morris_options.behaveLikeLine = true;
4655 state.morris_instance = new Morris.Area(state.morris_options);
4658 state.morris_instance = new Morris.Area(state.morris_options);
4663 // ----------------------------------------------------------------------------------------------------------------
4666 NETDATA.raphaelInitialize = function(callback) {
4667 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4669 url: NETDATA.raphael_js,
4672 xhrFields: { withCredentials: true } // required for the cookie
4675 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4678 NETDATA.chartLibraries.raphael.enabled = false;
4679 NETDATA.error(100, NETDATA.raphael_js);
4681 .always(function() {
4682 if(typeof callback === "function")
4687 NETDATA.chartLibraries.raphael.enabled = false;
4688 if(typeof callback === "function")
4693 NETDATA.raphaelChartUpdate = function(state, data) {
4694 $(state.element_chart).raphael(data.result, {
4695 width: state.chartWidth(),
4696 height: state.chartHeight()
4702 NETDATA.raphaelChartCreate = function(state, data) {
4703 $(state.element_chart).raphael(data.result, {
4704 width: state.chartWidth(),
4705 height: state.chartHeight()
4711 // ----------------------------------------------------------------------------------------------------------------
4714 NETDATA.c3Initialize = function(callback) {
4715 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4718 if(!NETDATA.chartLibraries.d3.initialized) {
4719 if(NETDATA.chartLibraries.d3.enabled) {
4720 NETDATA.d3Initialize(function() {
4721 NETDATA.c3Initialize(callback);
4725 NETDATA.chartLibraries.c3.enabled = false;
4726 if(typeof callback === "function")
4731 NETDATA._loadCSS(NETDATA.c3_css);
4737 xhrFields: { withCredentials: true } // required for the cookie
4740 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4743 NETDATA.chartLibraries.c3.enabled = false;
4744 NETDATA.error(100, NETDATA.c3_js);
4746 .always(function() {
4747 if(typeof callback === "function")
4753 NETDATA.chartLibraries.c3.enabled = false;
4754 if(typeof callback === "function")
4759 NETDATA.c3ChartUpdate = function(state, data) {
4760 state.c3_instance.destroy();
4761 return NETDATA.c3ChartCreate(state, data);
4763 //state.c3_instance.load({
4764 // rows: data.result,
4771 NETDATA.c3ChartCreate = function(state, data) {
4773 state.element_chart.id = 'c3-' + state.uuid;
4774 // console.log('id = ' + state.element_chart.id);
4776 state.c3_instance = c3.generate({
4777 bindto: '#' + state.element_chart.id,
4779 width: state.chartWidth(),
4780 height: state.chartHeight()
4783 pattern: state.chartColors()
4788 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4794 format: function(x) {
4795 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4822 // console.log(state.c3_instance);
4827 // ----------------------------------------------------------------------------------------------------------------
4830 NETDATA.d3Initialize = function(callback) {
4831 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4836 xhrFields: { withCredentials: true } // required for the cookie
4839 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4842 NETDATA.chartLibraries.d3.enabled = false;
4843 NETDATA.error(100, NETDATA.d3_js);
4845 .always(function() {
4846 if(typeof callback === "function")
4851 NETDATA.chartLibraries.d3.enabled = false;
4852 if(typeof callback === "function")
4857 NETDATA.d3ChartUpdate = function(state, data) {
4861 NETDATA.d3ChartCreate = function(state, data) {
4865 // ----------------------------------------------------------------------------------------------------------------
4868 NETDATA.googleInitialize = function(callback) {
4869 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4871 url: NETDATA.google_js,
4874 xhrFields: { withCredentials: true } // required for the cookie
4877 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4878 google.load('visualization', '1.1', {
4879 'packages': ['corechart', 'controls'],
4880 'callback': callback
4884 NETDATA.chartLibraries.google.enabled = false;
4885 NETDATA.error(100, NETDATA.google_js);
4886 if(typeof callback === "function")
4891 NETDATA.chartLibraries.google.enabled = false;
4892 if(typeof callback === "function")
4897 NETDATA.googleChartUpdate = function(state, data) {
4898 var datatable = new google.visualization.DataTable(data.result);
4899 state.google_instance.draw(datatable, state.google_options);
4903 NETDATA.googleChartCreate = function(state, data) {
4904 var datatable = new google.visualization.DataTable(data.result);
4906 state.google_options = {
4907 colors: state.chartColors(),
4909 // do not set width, height - the chart resizes itself
4910 //width: state.chartWidth(),
4911 //height: state.chartHeight(),
4916 // title: "Time of Day",
4917 // format:'HH:mm:ss',
4918 viewWindowMode: 'maximized',
4930 viewWindowMode: 'pretty',
4945 focusTarget: 'category',
4952 titlePosition: 'out',
4963 curveType: 'function',
4968 switch(state.chart.chart_type) {
4970 state.google_options.vAxis.viewWindowMode = 'maximized';
4971 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4972 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4976 state.google_options.isStacked = true;
4977 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
4978 state.google_options.vAxis.viewWindowMode = 'maximized';
4979 state.google_options.vAxis.minValue = null;
4980 state.google_options.vAxis.maxValue = null;
4981 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4986 state.google_options.lineWidth = 2;
4987 state.google_instance = new google.visualization.LineChart(state.element_chart);
4991 state.google_instance.draw(datatable, state.google_options);
4995 // ----------------------------------------------------------------------------------------------------------------
4997 NETDATA.percentFromValueMax = function(value, max) {
4998 if(value === null) value = 0;
4999 if(max < value) max = value;
5003 pcent = Math.round(value * 100 / max);
5004 if(pcent === 0 && value > 0) pcent = 1;
5010 // ----------------------------------------------------------------------------------------------------------------
5013 NETDATA.easypiechartInitialize = function(callback) {
5014 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5016 url: NETDATA.easypiechart_js,
5019 xhrFields: { withCredentials: true } // required for the cookie
5022 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5025 NETDATA.chartLibraries.easypiechart.enabled = false;
5026 NETDATA.error(100, NETDATA.easypiechart_js);
5028 .always(function() {
5029 if(typeof callback === "function")
5034 NETDATA.chartLibraries.easypiechart.enabled = false;
5035 if(typeof callback === "function")
5040 NETDATA.easypiechartClearSelection = function(state) {
5041 if(typeof state.easyPieChartEvent !== 'undefined') {
5042 if(state.easyPieChartEvent.timer !== null)
5043 clearTimeout(state.easyPieChartEvent.timer);
5045 state.easyPieChartEvent.timer = null;
5048 if(state.isAutoRefreshable() === true && state.data !== null) {
5049 NETDATA.easypiechartChartUpdate(state, state.data);
5052 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
5053 state.easyPieChart_instance.update(0);
5055 state.easyPieChart_instance.enableAnimation();
5060 NETDATA.easypiechartSetSelection = function(state, t) {
5061 if(state.timeIsVisible(t) !== true)
5062 return NETDATA.easypiechartClearSelection(state);
5064 var slot = state.calculateRowForTime(t);
5065 if(slot < 0 || slot >= state.data.result.length)
5066 return NETDATA.easypiechartClearSelection(state);
5068 if(typeof state.easyPieChartEvent === 'undefined') {
5069 state.easyPieChartEvent = {
5076 var value = state.data.result[state.data.result.length - 1 - slot];
5077 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5078 var pcent = NETDATA.percentFromValueMax(value, max);
5080 state.easyPieChartEvent.value = value;
5081 state.easyPieChartEvent.pcent = pcent;
5082 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
5084 if(state.easyPieChartEvent.timer === null) {
5085 state.easyPieChart_instance.disableAnimation();
5087 state.easyPieChartEvent.timer = setTimeout(function() {
5088 state.easyPieChartEvent.timer = null;
5089 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5090 }, NETDATA.options.current.charts_selection_animation_delay);
5096 NETDATA.easypiechartChartUpdate = function(state, data) {
5097 var value, max, pcent;
5099 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5105 value = data.result[0];
5106 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5107 pcent = NETDATA.percentFromValueMax(value, max);
5110 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
5111 state.easyPieChart_instance.update(pcent);
5115 NETDATA.easypiechartChartCreate = function(state, data) {
5116 var self = $(state.element);
5117 var chart = $(state.element_chart);
5119 var value = data.result[0];
5120 var max = self.data('easypiechart-max-value') || null;
5121 var adjust = self.data('easypiechart-adjust') || null;
5124 max = NETDATA.commonMax.get(state);
5125 state.easyPieChartMax = null;
5128 state.easyPieChartMax = max;
5130 var pcent = NETDATA.percentFromValueMax(value, max);
5132 chart.data('data-percent', pcent);
5136 case 'width': size = state.chartHeight(); break;
5137 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5138 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5140 default: size = state.chartWidth(); break;
5142 state.element.style.width = size + 'px';
5143 state.element.style.height = size + 'px';
5145 var stroke = Math.floor(size / 22);
5146 if(stroke < 3) stroke = 2;
5148 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5149 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5150 state.easyPieChartLabel = document.createElement('span');
5151 state.easyPieChartLabel.className = 'easyPieChartLabel';
5152 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
5153 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5154 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5155 state.element_chart.appendChild(state.easyPieChartLabel);
5157 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5158 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5159 state.easyPieChartTitle = document.createElement('span');
5160 state.easyPieChartTitle.className = 'easyPieChartTitle';
5161 state.easyPieChartTitle.innerHTML = state.title;
5162 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5163 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5164 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5165 state.element_chart.appendChild(state.easyPieChartTitle);
5167 var unitfontsize = Math.round(titlefontsize * 0.9);
5168 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5169 state.easyPieChartUnits = document.createElement('span');
5170 state.easyPieChartUnits.className = 'easyPieChartUnits';
5171 state.easyPieChartUnits.innerHTML = state.units;
5172 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5173 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5174 state.element_chart.appendChild(state.easyPieChartUnits);
5176 var barColor = self.data('easypiechart-barcolor');
5177 if(typeof barColor === 'undefined' || barColor === null)
5178 barColor = state.chartColors()[0];
5180 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5181 var tmp = eval(barColor);
5182 if(typeof tmp === 'function')
5186 chart.easyPieChart({
5188 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5189 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5190 scaleLength: self.data('easypiechart-scalelength') || 5,
5191 lineCap: self.data('easypiechart-linecap') || 'round',
5192 lineWidth: self.data('easypiechart-linewidth') || stroke,
5193 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5194 size: self.data('easypiechart-size') || size,
5195 rotate: self.data('easypiechart-rotate') || 0,
5196 animate: self.data('easypiechart-rotate') || {duration: 500, enabled: true},
5197 easing: self.data('easypiechart-easing') || undefined
5200 // when we just re-create the chart
5201 // do not animate the first update
5203 if(typeof state.easyPieChart_instance !== 'undefined')
5206 state.easyPieChart_instance = chart.data('easyPieChart');
5207 if(animate === false) state.easyPieChart_instance.disableAnimation();
5208 state.easyPieChart_instance.update(pcent);
5209 if(animate === false) state.easyPieChart_instance.enableAnimation();
5213 // ----------------------------------------------------------------------------------------------------------------
5216 NETDATA.gaugeInitialize = function(callback) {
5217 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5219 url: NETDATA.gauge_js,
5222 xhrFields: { withCredentials: true } // required for the cookie
5225 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5228 NETDATA.chartLibraries.gauge.enabled = false;
5229 NETDATA.error(100, NETDATA.gauge_js);
5231 .always(function() {
5232 if(typeof callback === "function")
5237 NETDATA.chartLibraries.gauge.enabled = false;
5238 if(typeof callback === "function")
5243 NETDATA.gaugeAnimation = function(state, status) {
5246 if(typeof status === 'boolean' && status === false)
5248 else if(typeof status === 'number')
5251 // console.log('gauge speed ' + speed);
5252 state.gauge_instance.animationSpeed = speed;
5253 state.___gaugeOld__.speed = speed;
5256 NETDATA.gaugeSet = function(state, value, min, max) {
5257 if(typeof value !== 'number') value = 0;
5258 if(typeof min !== 'number') min = 0;
5259 if(typeof max !== 'number') max = 0;
5260 if(value > max) max = value;
5261 if(value < min) min = value;
5270 // gauge.js has an issue if the needle
5271 // is smaller than min or larger than max
5272 // when we set the new values
5273 // the needle will go crazy
5275 // to prevent it, we always feed it
5276 // with a percentage, so that the needle
5277 // is always between min and max
5278 var pcent = (value - min) * 100 / (max - min);
5280 // these should never happen
5281 if(pcent < 0) pcent = 0;
5282 if(pcent > 100) pcent = 100;
5284 state.gauge_instance.set(pcent);
5285 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5287 state.___gaugeOld__.value = value;
5288 state.___gaugeOld__.min = min;
5289 state.___gaugeOld__.max = max;
5292 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5293 if(state.___gaugeOld__.valueLabel !== value) {
5294 state.___gaugeOld__.valueLabel = value;
5295 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
5297 if(state.___gaugeOld__.minLabel !== min) {
5298 state.___gaugeOld__.minLabel = min;
5299 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
5301 if(state.___gaugeOld__.maxLabel !== max) {
5302 state.___gaugeOld__.maxLabel = max;
5303 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
5307 NETDATA.gaugeClearSelection = function(state) {
5308 if(typeof state.gaugeEvent !== 'undefined') {
5309 if(state.gaugeEvent.timer !== null)
5310 clearTimeout(state.gaugeEvent.timer);
5312 state.gaugeEvent.timer = null;
5315 if(state.isAutoRefreshable() === true && state.data !== null) {
5316 NETDATA.gaugeChartUpdate(state, state.data);
5319 NETDATA.gaugeAnimation(state, false);
5320 NETDATA.gaugeSet(state, null, null, null);
5321 NETDATA.gaugeSetLabels(state, null, null, null);
5324 NETDATA.gaugeAnimation(state, true);
5328 NETDATA.gaugeSetSelection = function(state, t) {
5329 if(state.timeIsVisible(t) !== true)
5330 return NETDATA.gaugeClearSelection(state);
5332 var slot = state.calculateRowForTime(t);
5333 if(slot < 0 || slot >= state.data.result.length)
5334 return NETDATA.gaugeClearSelection(state);
5336 if(typeof state.gaugeEvent === 'undefined') {
5337 state.gaugeEvent = {
5345 var value = state.data.result[state.data.result.length - 1 - slot];
5346 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5349 state.gaugeEvent.value = value;
5350 state.gaugeEvent.max = max;
5351 state.gaugeEvent.min = min;
5352 NETDATA.gaugeSetLabels(state, value, min, max);
5354 if(state.gaugeEvent.timer === null) {
5355 NETDATA.gaugeAnimation(state, false);
5357 state.gaugeEvent.timer = setTimeout(function() {
5358 state.gaugeEvent.timer = null;
5359 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5360 }, NETDATA.options.current.charts_selection_animation_delay);
5366 NETDATA.gaugeChartUpdate = function(state, data) {
5367 var value, min, max;
5369 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5373 NETDATA.gaugeSetLabels(state, null, null, null);
5376 value = data.result[0];
5378 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5379 if(value > max) max = value;
5380 NETDATA.gaugeSetLabels(state, value, min, max);
5383 NETDATA.gaugeSet(state, value, min, max);
5387 NETDATA.gaugeChartCreate = function(state, data) {
5388 var self = $(state.element);
5389 // var chart = $(state.element_chart);
5391 var value = data.result[0];
5392 var max = self.data('gauge-max-value') || null;
5393 var adjust = self.data('gauge-adjust') || null;
5394 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5395 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5396 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5397 var stopColor = self.data('gauge-stop-color') || void 0;
5398 var generateGradient = self.data('gauge-generate-gradient') || false;
5401 max = NETDATA.commonMax.get(state);
5402 state.gaugeMax = null;
5405 state.gaugeMax = max;
5407 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5409 // case 'width': width = height * ratio; break;
5411 // default: height = width / ratio; break;
5413 //state.element.style.width = width.toString() + 'px';
5414 //state.element.style.height = height.toString() + 'px';
5419 lines: 12, // The number of lines to draw
5420 angle: 0.15, // The length of each line
5421 lineWidth: 0.44, // 0.44 The line thickness
5423 length: 0.8, // 0.9 The radius of the inner circle
5424 strokeWidth: 0.035, // The rotation offset
5425 color: pointerColor // Fill color
5427 colorStart: startColor, // Colors
5428 colorStop: stopColor, // just experiment with them
5429 strokeColor: strokeColor, // to see which ones work best for you
5431 generateGradient: (generateGradient === true)?true:false,
5435 if (generateGradient.constructor === Array) {
5437 // data-gauge-generate-gradient="[0, 50, 100]"
5438 // data-gauge-gradient-percent-color-0="#FFFFFF"
5439 // data-gauge-gradient-percent-color-50="#999900"
5440 // data-gauge-gradient-percent-color-100="#000000"
5442 options.percentColors = new Array();
5443 var len = generateGradient.length;
5445 var pcent = generateGradient[len];
5446 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5447 if(color !== false) {
5448 var a = new Array();
5451 options.percentColors.unshift(a);
5454 if(options.percentColors.length === 0)
5455 delete options.percentColors;
5457 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5458 options.percentColors = [
5459 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5460 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5461 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5462 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5463 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5464 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5465 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5466 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5467 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5468 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5469 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5472 state.gauge_canvas = document.createElement('canvas');
5473 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5474 state.gauge_canvas.className = 'gaugeChart';
5475 state.gauge_canvas.width = width;
5476 state.gauge_canvas.height = height;
5477 state.element_chart.appendChild(state.gauge_canvas);
5479 var valuefontsize = Math.floor(height / 6);
5480 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5481 state.gaugeChartLabel = document.createElement('span');
5482 state.gaugeChartLabel.className = 'gaugeChartLabel';
5483 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5484 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5485 state.element_chart.appendChild(state.gaugeChartLabel);
5487 var titlefontsize = Math.round(valuefontsize / 2);
5489 state.gaugeChartTitle = document.createElement('span');
5490 state.gaugeChartTitle.className = 'gaugeChartTitle';
5491 state.gaugeChartTitle.innerHTML = state.title;
5492 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5493 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5494 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5495 state.element_chart.appendChild(state.gaugeChartTitle);
5497 var unitfontsize = Math.round(titlefontsize * 0.9);
5498 state.gaugeChartUnits = document.createElement('span');
5499 state.gaugeChartUnits.className = 'gaugeChartUnits';
5500 state.gaugeChartUnits.innerHTML = state.units;
5501 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5502 state.element_chart.appendChild(state.gaugeChartUnits);
5504 state.gaugeChartMin = document.createElement('span');
5505 state.gaugeChartMin.className = 'gaugeChartMin';
5506 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5507 state.element_chart.appendChild(state.gaugeChartMin);
5509 state.gaugeChartMax = document.createElement('span');
5510 state.gaugeChartMax.className = 'gaugeChartMax';
5511 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5512 state.element_chart.appendChild(state.gaugeChartMax);
5514 // when we just re-create the chart
5515 // do not animate the first update
5517 if(typeof state.gauge_instance !== 'undefined')
5520 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5522 state.___gaugeOld__ = {
5531 // we will always feed a percentage
5532 state.gauge_instance.minValue = 0;
5533 state.gauge_instance.maxValue = 100;
5535 NETDATA.gaugeAnimation(state, animate);
5536 NETDATA.gaugeSet(state, value, 0, max);
5537 NETDATA.gaugeSetLabels(state, value, 0, max);
5538 NETDATA.gaugeAnimation(state, true);
5542 // ----------------------------------------------------------------------------------------------------------------
5543 // Charts Libraries Registration
5545 NETDATA.chartLibraries = {
5547 initialize: NETDATA.dygraphInitialize,
5548 create: NETDATA.dygraphChartCreate,
5549 update: NETDATA.dygraphChartUpdate,
5550 resize: function(state) {
5551 if(typeof state.dygraph_instance.resize === 'function')
5552 state.dygraph_instance.resize();
5554 setSelection: NETDATA.dygraphSetSelection,
5555 clearSelection: NETDATA.dygraphClearSelection,
5556 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5559 format: function(state) { return 'json'; },
5560 options: function(state) { return 'ms|flip'; },
5561 legend: function(state) {
5562 if(this.isSparkline(state) === false)
5563 return 'right-side';
5567 autoresize: function(state) { return true; },
5568 max_updates_to_recreate: function(state) { return 5000; },
5569 track_colors: function(state) { return true; },
5570 pixels_per_point: function(state) {
5571 if(this.isSparkline(state) === false)
5577 isSparkline: function(state) {
5578 if(typeof state.dygraph_sparkline === 'undefined') {
5579 var t = $(state.element).data('dygraph-theme');
5580 if(t === 'sparkline')
5581 state.dygraph_sparkline = true;
5583 state.dygraph_sparkline = false;
5585 return state.dygraph_sparkline;
5589 initialize: NETDATA.sparklineInitialize,
5590 create: NETDATA.sparklineChartCreate,
5591 update: NETDATA.sparklineChartUpdate,
5593 setSelection: undefined, // function(state, t) { return true; },
5594 clearSelection: undefined, // function(state) { return true; },
5595 toolboxPanAndZoom: null,
5598 format: function(state) { return 'array'; },
5599 options: function(state) { return 'flip|abs'; },
5600 legend: function(state) { return null; },
5601 autoresize: function(state) { return false; },
5602 max_updates_to_recreate: function(state) { return 5000; },
5603 track_colors: function(state) { return false; },
5604 pixels_per_point: function(state) { return 3; }
5607 initialize: NETDATA.peityInitialize,
5608 create: NETDATA.peityChartCreate,
5609 update: NETDATA.peityChartUpdate,
5611 setSelection: undefined, // function(state, t) { return true; },
5612 clearSelection: undefined, // function(state) { return true; },
5613 toolboxPanAndZoom: null,
5616 format: function(state) { return 'ssvcomma'; },
5617 options: function(state) { return 'null2zero|flip|abs'; },
5618 legend: function(state) { return null; },
5619 autoresize: function(state) { return false; },
5620 max_updates_to_recreate: function(state) { return 5000; },
5621 track_colors: function(state) { return false; },
5622 pixels_per_point: function(state) { return 3; }
5625 initialize: NETDATA.morrisInitialize,
5626 create: NETDATA.morrisChartCreate,
5627 update: NETDATA.morrisChartUpdate,
5629 setSelection: undefined, // function(state, t) { return true; },
5630 clearSelection: undefined, // function(state) { return true; },
5631 toolboxPanAndZoom: null,
5634 format: function(state) { return 'json'; },
5635 options: function(state) { return 'objectrows|ms'; },
5636 legend: function(state) { return null; },
5637 autoresize: function(state) { return false; },
5638 max_updates_to_recreate: function(state) { return 50; },
5639 track_colors: function(state) { return false; },
5640 pixels_per_point: function(state) { return 15; }
5643 initialize: NETDATA.googleInitialize,
5644 create: NETDATA.googleChartCreate,
5645 update: NETDATA.googleChartUpdate,
5647 setSelection: undefined, //function(state, t) { return true; },
5648 clearSelection: undefined, //function(state) { return true; },
5649 toolboxPanAndZoom: null,
5652 format: function(state) { return 'datatable'; },
5653 options: function(state) { return ''; },
5654 legend: function(state) { return null; },
5655 autoresize: function(state) { return false; },
5656 max_updates_to_recreate: function(state) { return 300; },
5657 track_colors: function(state) { return false; },
5658 pixels_per_point: function(state) { return 4; }
5661 initialize: NETDATA.raphaelInitialize,
5662 create: NETDATA.raphaelChartCreate,
5663 update: NETDATA.raphaelChartUpdate,
5665 setSelection: undefined, // function(state, t) { return true; },
5666 clearSelection: undefined, // function(state) { return true; },
5667 toolboxPanAndZoom: null,
5670 format: function(state) { return 'json'; },
5671 options: function(state) { return ''; },
5672 legend: function(state) { return null; },
5673 autoresize: function(state) { return false; },
5674 max_updates_to_recreate: function(state) { return 5000; },
5675 track_colors: function(state) { return false; },
5676 pixels_per_point: function(state) { return 3; }
5679 initialize: NETDATA.c3Initialize,
5680 create: NETDATA.c3ChartCreate,
5681 update: NETDATA.c3ChartUpdate,
5683 setSelection: undefined, // function(state, t) { return true; },
5684 clearSelection: undefined, // function(state) { return true; },
5685 toolboxPanAndZoom: null,
5688 format: function(state) { return 'csvjsonarray'; },
5689 options: function(state) { return 'milliseconds'; },
5690 legend: function(state) { return null; },
5691 autoresize: function(state) { return false; },
5692 max_updates_to_recreate: function(state) { return 5000; },
5693 track_colors: function(state) { return false; },
5694 pixels_per_point: function(state) { return 15; }
5697 initialize: NETDATA.d3Initialize,
5698 create: NETDATA.d3ChartCreate,
5699 update: NETDATA.d3ChartUpdate,
5701 setSelection: undefined, // function(state, t) { return true; },
5702 clearSelection: undefined, // function(state) { return true; },
5703 toolboxPanAndZoom: null,
5706 format: function(state) { return 'json'; },
5707 options: function(state) { return ''; },
5708 legend: function(state) { return null; },
5709 autoresize: function(state) { return false; },
5710 max_updates_to_recreate: function(state) { return 5000; },
5711 track_colors: function(state) { return false; },
5712 pixels_per_point: function(state) { return 3; }
5715 initialize: NETDATA.easypiechartInitialize,
5716 create: NETDATA.easypiechartChartCreate,
5717 update: NETDATA.easypiechartChartUpdate,
5719 setSelection: NETDATA.easypiechartSetSelection,
5720 clearSelection: NETDATA.easypiechartClearSelection,
5721 toolboxPanAndZoom: null,
5724 format: function(state) { return 'array'; },
5725 options: function(state) { return 'absolute'; },
5726 legend: function(state) { return null; },
5727 autoresize: function(state) { return false; },
5728 max_updates_to_recreate: function(state) { return 5000; },
5729 track_colors: function(state) { return true; },
5730 pixels_per_point: function(state) { return 3; },
5734 initialize: NETDATA.gaugeInitialize,
5735 create: NETDATA.gaugeChartCreate,
5736 update: NETDATA.gaugeChartUpdate,
5738 setSelection: NETDATA.gaugeSetSelection,
5739 clearSelection: NETDATA.gaugeClearSelection,
5740 toolboxPanAndZoom: null,
5743 format: function(state) { return 'array'; },
5744 options: function(state) { return 'absolute'; },
5745 legend: function(state) { return null; },
5746 autoresize: function(state) { return false; },
5747 max_updates_to_recreate: function(state) { return 5000; },
5748 track_colors: function(state) { return true; },
5749 pixels_per_point: function(state) { return 3; },
5754 NETDATA.registerChartLibrary = function(library, url) {
5755 if(NETDATA.options.debug.libraries === true)
5756 console.log("registering chart library: " + library);
5758 NETDATA.chartLibraries[library].url = url;
5759 NETDATA.chartLibraries[library].initialized = true;
5760 NETDATA.chartLibraries[library].enabled = true;
5763 // ----------------------------------------------------------------------------------------------------------------
5764 // Load required JS libraries and CSS
5766 NETDATA.requiredJs = [
5768 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5770 isAlreadyLoaded: function() {
5771 // check if bootstrap is loaded
5772 if(typeof $().emulateTransitionEnd == 'function')
5775 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5783 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller-0.8.7.min.js',
5784 isAlreadyLoaded: function() { return false; }
5788 NETDATA.requiredCSS = [
5790 url: NETDATA.themes.current.bootstrap_css,
5791 isAlreadyLoaded: function() {
5792 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5799 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
5800 isAlreadyLoaded: function() { return false; }
5803 url: NETDATA.themes.current.dashboard_css,
5804 isAlreadyLoaded: function() { return false; }
5808 NETDATA.loadedRequiredJs = 0;
5809 NETDATA.loadRequiredJs = function(index, callback) {
5810 if(index >= NETDATA.requiredJs.length) {
5811 if(typeof callback === 'function')
5816 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5817 NETDATA.loadedRequiredJs++;
5818 NETDATA.loadRequiredJs(++index, callback);
5822 if(NETDATA.options.debug.main_loop === true)
5823 console.log('loading ' + NETDATA.requiredJs[index].url);
5826 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5830 url: NETDATA.requiredJs[index].url,
5833 xhrFields: { withCredentials: true } // required for the cookie
5836 if(NETDATA.options.debug.main_loop === true)
5837 console.log('loaded ' + NETDATA.requiredJs[index].url);
5840 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5842 .always(function() {
5843 NETDATA.loadedRequiredJs++;
5846 NETDATA.loadRequiredJs(++index, callback);
5850 NETDATA.loadRequiredJs(++index, callback);
5853 NETDATA.loadRequiredCSS = function(index) {
5854 if(index >= NETDATA.requiredCSS.length)
5857 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5858 NETDATA.loadRequiredCSS(++index);
5862 if(NETDATA.options.debug.main_loop === true)
5863 console.log('loading ' + NETDATA.requiredCSS[index].url);
5865 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5866 NETDATA.loadRequiredCSS(++index);
5870 // ----------------------------------------------------------------------------------------------------------------
5871 // Registry of netdata hosts
5874 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
5875 chart_div_offset: 100, // give that space above the chart when scrolling to it
5876 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
5877 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
5879 ms_penalty: 0, // the time penalty of the next alarm
5880 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
5881 // if alarms are shown faster than: one per 500ms
5883 notifications: false, // when true, the browser supports notifications (may not be granted though)
5884 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
5885 first_notification_id: 0, // the id of the first alarm_log entry for this session
5886 // this is used to prevent CLEAR notifications for past events
5887 // notifications_shown: new Array(),
5889 server: null, // the server to connect to for fetching alarms
5890 current: null, // the list of raised alarms - updated in the background
5891 callback: null, // a callback function to call every time the list of raised alarms is refreshed
5893 notify: function(entry) {
5894 // console.log('alarm ' + entry.unique_id);
5896 if(entry.updated === true) {
5897 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
5901 var value = entry.value;
5902 if(NETDATA.alarms.current !== null) {
5903 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
5904 if(typeof t !== 'undefined' && entry.status == t.status)
5908 var name = entry.name.replace(/_/g, ' ');
5909 var status = entry.status.toLowerCase();
5910 var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
5911 var tag = entry.alarm_id;
5912 var icon = 'images/seo-performance-128.png';
5913 var interaction = false;
5917 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
5919 switch(entry.status) {
5927 case 'UNINITIALIZED':
5931 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
5932 // console.log('alarm ' + entry.unique_id + ' is not current');
5935 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
5936 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
5939 title = name + ' back to normal';
5940 icon = 'images/check-mark-2-128-green.png'
5941 interaction = false;
5945 if(entry.old_status === 'CRITICAL')
5946 status = 'demoted to ' + entry.status.toLowerCase();
5948 icon = 'images/alert-128-orange.png';
5949 interaction = false;
5953 if(entry.old_status === 'WARNING')
5954 status = 'escalated to ' + entry.status.toLowerCase();
5956 icon = 'images/alert-128-red.png'
5961 console.log('invalid alarm status ' + entry.status);
5966 // cleanup old notifications with the same alarm_id as this one
5967 // FIXME: it does not seem to work on any web browser!
5968 var len = NETDATA.alarms.notifications_shown.length;
5970 var n = NETDATA.alarms.notifications_shown[len];
5971 if(n.data.alarm_id === entry.alarm_id) {
5972 console.log('removing old alarm ' + n.data.unique_id);
5974 // close the notification
5977 // remove it from the array
5978 NETDATA.alarms.notifications_shown.splice(len, 1);
5979 len = NETDATA.alarms.notifications_shown.length;
5986 setTimeout(function() {
5987 // show this notification
5988 // console.log('new notification: ' + title);
5989 var n = new Notification(title, {
5990 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
5992 requireInteraction: interaction,
5993 icon: NETDATA.serverDefault + icon,
5997 n.onclick = function(event) {
5998 event.preventDefault();
5999 NETDATA.alarms.onclick(event.target.data);
6003 // NETDATA.alarms.notifications_shown.push(n);
6004 // console.log(entry);
6005 }, NETDATA.alarms.ms_penalty);
6007 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6011 scrollToChart: function(chart_id) {
6012 if(typeof chart_id === 'string') {
6013 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6014 if(typeof offset !== 'undefined') {
6015 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6022 scrollToAlarm: function(alarm) {
6023 if(typeof alarm === 'object') {
6024 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6026 if(ret === true && NETDATA.options.page_is_visible === false)
6028 // 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.');
6033 notifyAll: function() {
6034 // console.log('FETCHING ALARM LOG');
6035 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6036 // console.log('ALARM LOG FETCHED');
6038 if(data === null || typeof data !== 'object') {
6039 console.log('invalid alarms log response');
6043 if(data.length === 0) {
6044 console.log('received empty alarm log');
6048 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6050 data.sort(function(a, b) {
6051 if(a.unique_id > b.unique_id) return -1;
6052 if(a.unique_id < b.unique_id) return 1;
6056 NETDATA.alarms.ms_penalty = 0;
6058 var len = data.length;
6060 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6061 NETDATA.alarms.notify(data[len]);
6064 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6067 NETDATA.alarms.last_notification_id = data[0].unique_id;
6068 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6069 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6073 check_notifications: function() {
6074 // returns true if we should fire 1+ notifications
6076 if(NETDATA.alarms.notifications !== true) {
6077 // console.log('notifications not available');
6081 if(Notification.permission !== 'granted') {
6082 // console.log('notifications not granted');
6086 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6087 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6089 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6090 // console.log('new alarms detected');
6093 //else console.log('no new alarms');
6095 // else console.log('cannot process alarms');
6100 get: function(what, callback) {
6102 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6106 'Cache-Control': 'no-cache, no-store',
6107 'Pragma': 'no-cache'
6109 xhrFields: { withCredentials: true } // required for the cookie
6111 .done(function(data) {
6112 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6113 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6115 if(typeof callback === 'function')
6119 NETDATA.error(415, NETDATA.alarms.server);
6121 if(typeof callback === 'function')
6126 update_forever: function() {
6127 NETDATA.alarms.get('active', function(data) {
6129 NETDATA.alarms.current = data;
6131 if(NETDATA.alarms.check_notifications() === true) {
6132 NETDATA.alarms.notifyAll();
6135 if (typeof NETDATA.alarms.callback === 'function') {
6136 NETDATA.alarms.callback(data);
6139 // Health monitoring is disabled on this netdata
6140 if(data.status === false) return;
6143 setTimeout(NETDATA.alarms.update_forever, 10000);
6147 get_log: function(last_id, callback) {
6148 // console.log('fetching all log after ' + last_id.toString());
6150 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6154 'Cache-Control': 'no-cache, no-store',
6155 'Pragma': 'no-cache'
6157 xhrFields: { withCredentials: true } // required for the cookie
6159 .done(function(data) {
6160 if(typeof callback === 'function')
6164 NETDATA.error(416, NETDATA.alarms.server);
6166 if(typeof callback === 'function')
6172 var host = NETDATA.serverDefault;
6173 while(host.slice(-1) === '/')
6174 host = host.substring(0, host.length - 1);
6175 NETDATA.alarms.server = host;
6177 NETDATA.alarms.last_notification_id = NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6179 if(NETDATA.alarms.onclick === null)
6180 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6182 if(netdataShowAlarms === true) {
6183 NETDATA.alarms.update_forever();
6185 if('Notification' in window) {
6186 // console.log('notifications available');
6187 NETDATA.alarms.notifications = true;
6189 if(Notification.permission === 'default')
6190 Notification.requestPermission();
6196 // ----------------------------------------------------------------------------------------------------------------
6197 // Registry of netdata hosts
6199 NETDATA.registry = {
6200 server: null, // the netdata registry server
6201 person_guid: null, // the unique ID of this browser / user
6202 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6203 hostname: null, // the hostname of the netdata server that served dashboard.js
6204 machines: null, // the user's other URLs
6205 machines_array: null, // the user's other URLs in an array
6208 parsePersonUrls: function(person_urls) {
6209 // console.log(person_urls);
6210 NETDATA.registry.person_urls = person_urls;
6213 NETDATA.registry.machines = {};
6214 NETDATA.registry.machines_array = new Array();
6216 var now = Date.now();
6217 var apu = person_urls;
6220 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6221 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6227 accesses: apu[i][3],
6229 alternate_urls: new Array()
6231 obj.alternate_urls.push(apu[i][1]);
6233 NETDATA.registry.machines[apu[i][0]] = obj;
6234 NETDATA.registry.machines_array.push(obj);
6237 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6239 var pu = NETDATA.registry.machines[apu[i][0]];
6240 if(pu.last_t < apu[i][2]) {
6242 pu.last_t = apu[i][2];
6243 pu.name = apu[i][4];
6245 pu.accesses += apu[i][3];
6246 pu.alternate_urls.push(apu[i][1]);
6251 if(typeof netdataRegistryCallback === 'function')
6252 netdataRegistryCallback(NETDATA.registry.machines_array);
6256 if(netdataRegistry !== true) return;
6258 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6260 NETDATA.registry.server = data.registry;
6261 NETDATA.registry.machine_guid = data.machine_guid;
6262 NETDATA.registry.hostname = data.hostname;
6264 NETDATA.registry.access(2, function (person_urls) {
6265 NETDATA.registry.parsePersonUrls(person_urls);
6272 hello: function(host, callback) {
6273 while(host.slice(-1) === '/')
6274 host = host.substring(0, host.length - 1);
6276 // send HELLO to a netdata server:
6277 // 1. verifies the server is reachable
6278 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6280 url: host + '/api/v1/registry?action=hello',
6284 'Cache-Control': 'no-cache, no-store',
6285 'Pragma': 'no-cache'
6287 xhrFields: { withCredentials: true } // required for the cookie
6289 .done(function(data) {
6290 if(typeof data.status !== 'string' || data.status !== 'ok') {
6291 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6295 if(typeof callback === 'function')
6299 NETDATA.error(407, host);
6301 if(typeof callback === 'function')
6306 access: function(max_redirects, callback) {
6307 // send ACCESS to a netdata registry:
6308 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6309 // 2. it responds with a list of netdata servers we know
6310 // the registry identifies us using a cookie it sets the first time we access it
6311 // the registry may respond with a redirect URL to send us to another registry
6313 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),
6317 'Cache-Control': 'no-cache, no-store',
6318 'Pragma': 'no-cache'
6320 xhrFields: { withCredentials: true } // required for the cookie
6322 .done(function(data) {
6323 var redirect = null;
6324 if(typeof data.registry === 'string')
6325 redirect = data.registry;
6327 if(typeof data.status !== 'string' || data.status !== 'ok') {
6328 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6333 if(redirect !== null && max_redirects > 0) {
6334 NETDATA.registry.server = redirect;
6335 NETDATA.registry.access(max_redirects - 1, callback);
6338 if(typeof callback === 'function')
6343 if(typeof data.person_guid === 'string')
6344 NETDATA.registry.person_guid = data.person_guid;
6346 if(typeof callback === 'function')
6347 callback(data.urls);
6351 NETDATA.error(410, NETDATA.registry.server);
6353 if(typeof callback === 'function')
6358 delete: function(delete_url, callback) {
6359 // send DELETE to a netdata registry:
6361 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),
6365 'Cache-Control': 'no-cache, no-store',
6366 'Pragma': 'no-cache'
6368 xhrFields: { withCredentials: true } // required for the cookie
6370 .done(function(data) {
6371 if(typeof data.status !== 'string' || data.status !== 'ok') {
6372 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6376 if(typeof callback === 'function')
6380 NETDATA.error(412, NETDATA.registry.server);
6382 if(typeof callback === 'function')
6387 search: function(machine_guid, callback) {
6388 // SEARCH for the URLs of a machine:
6390 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,
6394 'Cache-Control': 'no-cache, no-store',
6395 'Pragma': 'no-cache'
6397 xhrFields: { withCredentials: true } // required for the cookie
6399 .done(function(data) {
6400 if(typeof data.status !== 'string' || data.status !== 'ok') {
6401 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6405 if(typeof callback === 'function')
6409 NETDATA.error(418, NETDATA.registry.server);
6411 if(typeof callback === 'function')
6416 switch: function(new_person_guid, callback) {
6419 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,
6423 'Cache-Control': 'no-cache, no-store',
6424 'Pragma': 'no-cache'
6426 xhrFields: { withCredentials: true } // required for the cookie
6428 .done(function(data) {
6429 if(typeof data.status !== 'string' || data.status !== 'ok') {
6430 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6434 if(typeof callback === 'function')
6438 NETDATA.error(414, NETDATA.registry.server);
6440 if(typeof callback === 'function')
6446 // ----------------------------------------------------------------------------------------------------------------
6449 if(typeof netdataPrepCallback === 'function')
6450 netdataPrepCallback();
6452 NETDATA.errorReset();
6453 NETDATA.loadRequiredCSS(0);
6455 NETDATA._loadjQuery(function() {
6456 NETDATA.loadRequiredJs(0, function() {
6457 if(typeof $().emulateTransitionEnd !== 'function') {
6458 // bootstrap is not available
6459 NETDATA.options.current.show_help = false;
6462 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6463 if(NETDATA.options.debug.main_loop === true)
6464 console.log('starting chart refresh thread');
6471 // window.NETDATA = NETDATA;
6472 // })(window, document);