1 // You can set the following variables before loading this script:
3 // var netdataNoDygraphs = true; // do not use dygraph
4 // var netdataNoSparklines = true; // do not use sparkline
5 // var netdataNoPeitys = true; // do not use peity
6 // var netdataNoGoogleCharts = true; // do not use google
7 // var netdataNoMorris = true; // do not use morris
8 // var netdataNoEasyPieChart = true; // do not use easy pie chart
9 // var netdataNoGauge = true; // do not use gauge.js
10 // var netdataNoD3 = true; // do not use D3
11 // var netdataNoC3 = true; // do not use C3
12 // var netdataNoBootstrap = true; // do not load bootstrap
13 // var netdataDontStart = true; // do not start the thread to process the charts
14 // var netdataErrorCallback = null; // Callback function that will be invoked upon error
15 // var netdataRegistry = true; // Update the registry (default disabled)
16 // var netdataRegistryCallback = null; // Callback function that will be invoked with one param,
17 // the URLs from the registry
18 // var netdataShowHelp = false; // enable/disable help (default enabled)
19 // var netdataShowAlarms = true; // enable/disable alarms checks and notifications (default disabled)
21 // var netdataRegistryAfterMs = 1500 // the time to consult to registry on startup
23 // var netdataCallback = null; // a function to call when netdata is ready
24 // // netdata will be running while this is called (call NETDATA.pause to stop it)
25 // var netdataPrepCallback = null; // a callback to be called before netdata does anything else
27 // You can also set the default netdata server, using the following.
28 // When this variable is not set, we assume the page is hosted on your
29 // netdata server already.
30 // var netdataServer = "http://yourhost:19999"; // set your NetData server
32 //(function(window, document, undefined) {
34 // ------------------------------------------------------------------------
35 // compatibility fixes
37 // fix IE issue with console
38 if(!window.console) { window.console = { log: function(){} }; }
40 // if string.endsWith is not defined, define it
41 if(typeof String.prototype.endsWith !== 'function') {
42 String.prototype.endsWith = function(s) {
43 if(s.length > this.length) return false;
44 return this.slice(-s.length) === s;
48 // if string.startsWith is not defined, define it
49 if(typeof String.prototype.startsWith !== 'function') {
50 String.prototype.startsWith = function(s) {
51 if(s.length > this.length) return false;
52 return this.slice(s.length) === s;
57 var NETDATA = window.NETDATA || {};
59 NETDATA.name2id = function(s) {
68 // ----------------------------------------------------------------------------------------------------------------
69 // Detect the netdata server
71 // http://stackoverflow.com/questions/984510/what-is-my-script-src-url
72 // http://stackoverflow.com/questions/6941533/get-protocol-domain-and-port-from-url
73 NETDATA._scriptSource = function() {
76 if(typeof document.currentScript !== 'undefined') {
77 script = document.currentScript;
80 var all_scripts = document.getElementsByTagName('script');
81 script = all_scripts[all_scripts.length - 1];
84 if (typeof script.getAttribute.length !== 'undefined')
87 script = script.getAttribute('src', -1);
92 if(typeof netdataServer !== 'undefined')
93 NETDATA.serverDefault = netdataServer;
95 var s = NETDATA._scriptSource();
96 if(s) NETDATA.serverDefault = s.replace(/\/dashboard.js(\?.*)*$/g, "");
98 console.log('WARNING: Cannot detect the URL of the netdata server.');
99 NETDATA.serverDefault = null;
103 if(NETDATA.serverDefault === null)
104 NETDATA.serverDefault = '';
105 else if(NETDATA.serverDefault.slice(-1) !== '/')
106 NETDATA.serverDefault += '/';
108 // default URLs for all the external files we need
109 // make them RELATIVE so that the whole thing can also be
110 // installed under a web server
111 NETDATA.jQuery = NETDATA.serverDefault + 'lib/jquery-2.2.4.min.js';
112 NETDATA.peity_js = NETDATA.serverDefault + 'lib/jquery.peity-3.2.0.min.js';
113 NETDATA.sparkline_js = NETDATA.serverDefault + 'lib/jquery.sparkline-2.1.2.min.js';
114 NETDATA.easypiechart_js = NETDATA.serverDefault + 'lib/jquery.easypiechart-97b5824.min.js';
115 NETDATA.gauge_js = NETDATA.serverDefault + 'lib/gauge-d5260c3.min.js';
116 NETDATA.dygraph_js = NETDATA.serverDefault + 'lib/dygraph-combined-dd74404.js';
117 NETDATA.dygraph_smooth_js = NETDATA.serverDefault + 'lib/dygraph-smooth-plotter-dd74404.js';
118 NETDATA.raphael_js = NETDATA.serverDefault + 'lib/raphael-2.2.4-min.js';
119 NETDATA.c3_js = NETDATA.serverDefault + 'lib/c3-0.4.11.min.js';
120 NETDATA.c3_css = NETDATA.serverDefault + 'css/c3-0.4.11.min.css';
121 NETDATA.d3_js = NETDATA.serverDefault + 'lib/d3-3.5.17.min.js';
122 NETDATA.morris_js = NETDATA.serverDefault + 'lib/morris-0.5.1.min.js';
123 NETDATA.morris_css = NETDATA.serverDefault + 'css/morris-0.5.1.css';
124 NETDATA.google_js = 'https://www.google.com/jsapi';
128 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-3.3.7.css',
129 dashboard_css: NETDATA.serverDefault + 'dashboard.css?v20161002-1',
130 background: '#FFFFFF',
131 foreground: '#000000',
134 colors: [ '#3366CC', '#DC3912', '#109618', '#FF9900', '#990099', '#DD4477',
135 '#3B3EAC', '#66AA00', '#0099C6', '#B82E2E', '#AAAA11', '#5574A6',
136 '#994499', '#22AA99', '#6633CC', '#E67300', '#316395', '#8B0707',
137 '#329262', '#3B3EAC' ],
138 easypiechart_track: '#f0f0f0',
139 easypiechart_scale: '#dfe0e0',
140 gauge_pointer: '#C0C0C0',
141 gauge_stroke: '#F0F0F0',
142 gauge_gradient: false
145 bootstrap_css: NETDATA.serverDefault + 'css/bootstrap-slate-flat-3.3.7.css?v20161218-2',
146 dashboard_css: NETDATA.serverDefault + 'dashboard.slate.css?v20161218-1',
147 background: '#272b30',
148 foreground: '#C8C8C8',
151 /* colors: [ '#55bb33', '#ff2222', '#0099C6', '#faa11b', '#adbce0', '#DDDD00',
152 '#4178ba', '#f58122', '#a5cc39', '#f58667', '#f5ef89', '#cf93c0',
153 '#a5d18a', '#b8539d', '#3954a3', '#c8a9cf', '#c7de8a', '#fad20a',
154 '#a6a479', '#a66da8' ],
156 colors: [ '#66AA00', '#FE3912', '#3366CC', '#D66300', '#0099C6', '#DDDD00',
157 '#5054e6', '#EE9911', '#BB44CC', '#e45757', '#ef0aef', '#CC7700',
158 '#22AA99', '#109618', '#905bfd', '#f54882', '#4381bf', '#ff3737',
159 '#329262', '#3B3EFF' ],
160 easypiechart_track: '#373b40',
161 easypiechart_scale: '#373b40',
162 gauge_pointer: '#474b50',
163 gauge_stroke: '#373b40',
164 gauge_gradient: false
168 if(typeof netdataTheme !== 'undefined' && typeof NETDATA.themes[netdataTheme] !== 'undefined')
169 NETDATA.themes.current = NETDATA.themes[netdataTheme];
171 NETDATA.themes.current = NETDATA.themes.white;
173 NETDATA.colors = NETDATA.themes.current.colors;
175 // these are the colors Google Charts are using
176 // we have them here to attempt emulate their look and feel on the other chart libraries
177 // http://there4.io/2012/05/02/google-chart-color-list/
178 //NETDATA.colors = [ '#3366CC', '#DC3912', '#FF9900', '#109618', '#990099', '#3B3EAC', '#0099C6',
179 // '#DD4477', '#66AA00', '#B82E2E', '#316395', '#994499', '#22AA99', '#AAAA11',
180 // '#6633CC', '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC' ];
182 // an alternative set
183 // http://www.mulinblog.com/a-color-palette-optimized-for-data-visualization/
184 // (blue) (red) (orange) (green) (pink) (brown) (purple) (yellow) (gray)
185 //NETDATA.colors = [ '#5DA5DA', '#F15854', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#4D4D4D' ];
187 if(typeof netdataShowHelp === 'undefined')
188 netdataShowHelp = true;
190 if(typeof netdataShowAlarms === 'undefined')
191 netdataShowAlarms = false;
193 if(typeof netdataRegistryAfterMs !== 'number' || netdataRegistryAfterMs < 0)
194 netdataRegistryAfterMs = 1500;
196 if(typeof netdataRegistry === 'undefined') {
197 // backward compatibility
198 if(typeof netdataNoRegistry !== 'undefined' && netdataNoRegistry === false)
199 netdataRegistry = true;
201 netdataRegistry = false;
203 if(netdataRegistry === false && typeof netdataRegistryCallback === 'function')
204 netdataRegistry = true;
206 // ----------------------------------------------------------------------------------------------------------------
207 // the defaults for all charts
209 // if the user does not specify any of these, the following will be used
211 NETDATA.chartDefaults = {
212 host: NETDATA.serverDefault, // the server to get data from
213 width: '100%', // the chart width - can be null
214 height: '100%', // the chart height - can be null
215 min_width: null, // the chart minimum width - can be null
216 library: 'dygraph', // the graphing library to use
217 method: 'average', // the grouping method
218 before: 0, // panning
219 after: -600, // panning
220 pixels_per_point: 1, // the detail of the chart
221 fill_luminance: 0.8 // luminance of colors in solit areas
224 // ----------------------------------------------------------------------------------------------------------------
228 pauseCallback: null, // a callback when we are really paused
230 pause: false, // when enabled we don't auto-refresh the charts
232 targets: null, // an array of all the state objects that are
233 // currently active (independently of their
234 // viewport visibility)
236 updated_dom: true, // when true, the DOM has been updated with
237 // new elements we have to check.
239 auto_refresher_fast_weight: 0, // this is the current time in ms, spent
240 // rendering charts continiously.
241 // used with .current.fast_render_timeframe
243 page_is_visible: true, // when true, this page is visible
245 auto_refresher_stop_until: 0, // timestamp in ms - used internaly, to stop the
246 // auto-refresher for some time (when a chart is
247 // performing pan or zoom, we need to stop refreshing
248 // all other charts, to have the maximum speed for
249 // rendering the chart that is panned or zoomed).
250 // Used with .current.global_pan_sync_time
252 last_resized: Date.now(), // the timestamp of the last resize request
254 last_page_scroll: 0, // the timestamp the last time the page was scrolled
256 // the current profile
257 // we may have many...
259 pixels_per_point: 1, // the minimum pixels per point for all charts
260 // increase this to speed javascript up
261 // each chart library has its own limit too
262 // the max of this and the chart library is used
263 // the final is calculated every time, so a change
264 // here will have immediate effect on the next chart
267 idle_between_charts: 100, // ms - how much time to wait between chart updates
269 fast_render_timeframe: 200, // ms - render continously until this time of continious
270 // rendering has been reached
271 // this setting is used to make it render e.g. 10
272 // charts at once, sleep idle_between_charts time
273 // and continue for another 10 charts.
275 idle_between_loops: 500, // ms - if all charts have been updated, wait this
276 // time before starting again.
278 idle_parallel_loops: 100, // ms - the time between parallel refresher updates
280 idle_lost_focus: 500, // ms - when the window does not have focus, check
281 // if focus has been regained, every this time
283 global_pan_sync_time: 1000, // ms - when you pan or zoon a chart, the background
284 // autorefreshing of charts is paused for this amount
287 sync_selection_delay: 1500, // ms - when you pan or zoom a chart, wait this amount
288 // of time before setting up synchronized selections
291 sync_selection: true, // enable or disable selection sync
293 pan_and_zoom_delay: 50, // when panning or zooming, how ofter to update the chart
295 sync_pan_and_zoom: true, // enable or disable pan and zoom sync
297 pan_and_zoom_data_padding: true, // fetch more data for the master chart when panning or zooming
299 update_only_visible: true, // enable or disable visibility management
301 parallel_refresher: true, // enable parallel refresh of charts
303 concurrent_refreshes: true, // when parallel_refresher is enabled, sync also the charts
305 destroy_on_hide: false, // destroy charts when they are not visible
307 show_help: netdataShowHelp, // when enabled the charts will show some help
308 show_help_delay_show_ms: 500,
309 show_help_delay_hide_ms: 0,
311 eliminate_zero_dimensions: true, // do not show dimensions with just zeros
313 stop_updates_when_focus_is_lost: true, // boolean - shall we stop auto-refreshes when document does not have user focus
314 stop_updates_while_resizing: 1000, // ms - time to stop auto-refreshes while resizing the charts
316 double_click_speed: 500, // ms - time between clicks / taps to detect double click/tap
318 smooth_plot: true, // enable smooth plot, where possible
320 charts_selection_animation_delay: 50, // delay to animate charts when syncing selection
322 color_fill_opacity_line: 1.0,
323 color_fill_opacity_area: 0.2,
324 color_fill_opacity_stacked: 0.8,
326 pan_and_zoom_factor: 0.25, // the increment when panning and zooming with the toolbox
327 pan_and_zoom_factor_multiplier_control: 2.0,
328 pan_and_zoom_factor_multiplier_shift: 3.0,
329 pan_and_zoom_factor_multiplier_alt: 4.0,
331 abort_ajax_on_scroll: false, // kill pending ajax page scroll
332 async_on_scroll: false, // sync/async onscroll handler
333 onscroll_worker_duration_threshold: 30, // time in ms, to consider slow the onscroll handler
335 retries_on_data_failures: 3, // how many retries to make if we can't fetch chart data from the server
337 setOptionCallback: function() { ; }
345 chart_data_url: false,
346 chart_errors: false, // FIXME
354 NETDATA.statistics = {
357 refreshes_active_max: 0
361 // ----------------------------------------------------------------------------------------------------------------
362 // local storage options
364 NETDATA.localStorage = {
367 callback: {} // only used for resetting back to defaults
370 NETDATA.localStorageGet = function(key, def, callback) {
373 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
374 NETDATA.localStorage.default[key.toString()] = def;
375 NETDATA.localStorage.callback[key.toString()] = callback;
378 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
380 // console.log('localStorage: loading "' + key.toString() + '"');
381 ret = localStorage.getItem(key.toString());
382 // console.log('netdata loaded: ' + key.toString() + ' = ' + ret.toString());
383 if(ret === null || ret === 'undefined') {
384 // console.log('localStorage: cannot load it, saving "' + key.toString() + '" with value "' + JSON.stringify(def) + '"');
385 localStorage.setItem(key.toString(), JSON.stringify(def));
389 // console.log('localStorage: got "' + key.toString() + '" with value "' + ret + '"');
390 ret = JSON.parse(ret);
391 // console.log('localStorage: loaded "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
395 console.log('localStorage: failed to read "' + key.toString() + '", using default: "' + def.toString() + '"');
400 if(typeof ret === 'undefined' || ret === 'undefined') {
401 console.log('localStorage: LOADED UNDEFINED "' + key.toString() + '" as value ' + ret + ' of type ' + typeof(ret));
405 NETDATA.localStorage.current[key.toString()] = ret;
409 NETDATA.localStorageSet = function(key, value, callback) {
410 if(typeof value === 'undefined' || value === 'undefined') {
411 console.log('localStorage: ATTEMPT TO SET UNDEFINED "' + key.toString() + '" as value ' + value + ' of type ' + typeof(value));
414 if(typeof NETDATA.localStorage.default[key.toString()] === 'undefined') {
415 NETDATA.localStorage.default[key.toString()] = value;
416 NETDATA.localStorage.current[key.toString()] = value;
417 NETDATA.localStorage.callback[key.toString()] = callback;
420 if(typeof Storage !== "undefined" && typeof localStorage === 'object') {
421 // console.log('localStorage: saving "' + key.toString() + '" with value "' + JSON.stringify(value) + '"');
423 localStorage.setItem(key.toString(), JSON.stringify(value));
426 console.log('localStorage: failed to save "' + key.toString() + '" with value: "' + value.toString() + '"');
430 NETDATA.localStorage.current[key.toString()] = value;
434 NETDATA.localStorageGetRecursive = function(obj, prefix, callback) {
436 if(typeof obj[i] === 'object') {
437 //console.log('object ' + prefix + '.' + i.toString());
438 NETDATA.localStorageGetRecursive(obj[i], prefix + '.' + i.toString(), callback);
442 obj[i] = NETDATA.localStorageGet(prefix + '.' + i.toString(), obj[i], callback);
446 NETDATA.setOption = function(key, value) {
447 if(key.toString() === 'setOptionCallback') {
448 if(typeof NETDATA.options.current.setOptionCallback === 'function') {
449 NETDATA.options.current[key.toString()] = value;
450 NETDATA.options.current.setOptionCallback();
453 else if(NETDATA.options.current[key.toString()] !== value) {
454 var name = 'options.' + key.toString();
456 if(typeof NETDATA.localStorage.default[name.toString()] === 'undefined')
457 console.log('localStorage: setOption() on unsaved option: "' + name.toString() + '", value: ' + value);
459 //console.log(NETDATA.localStorage);
460 //console.log('setOption: setting "' + key.toString() + '" to "' + value + '" of type ' + typeof(value) + ' original type ' + typeof(NETDATA.options.current[key.toString()]));
461 //console.log(NETDATA.options);
462 NETDATA.options.current[key.toString()] = NETDATA.localStorageSet(name.toString(), value, null);
464 if(typeof NETDATA.options.current.setOptionCallback === 'function')
465 NETDATA.options.current.setOptionCallback();
471 NETDATA.getOption = function(key) {
472 return NETDATA.options.current[key.toString()];
475 // read settings from local storage
476 NETDATA.localStorageGetRecursive(NETDATA.options.current, 'options', null);
478 // always start with this option enabled.
479 NETDATA.setOption('stop_updates_when_focus_is_lost', true);
481 NETDATA.resetOptions = function() {
482 for(var i in NETDATA.localStorage.default) {
483 var a = i.split('.');
485 if(a[0] === 'options') {
486 if(a[1] === 'setOptionCallback') continue;
487 if(typeof NETDATA.localStorage.default[i] === 'undefined') continue;
488 if(NETDATA.options.current[i] === NETDATA.localStorage.default[i]) continue;
490 NETDATA.setOption(a[1], NETDATA.localStorage.default[i]);
492 else if(a[0] === 'chart_heights') {
493 if(typeof NETDATA.localStorage.callback[i] === 'function' && typeof NETDATA.localStorage.default[i] !== 'undefined') {
494 NETDATA.localStorage.callback[i](NETDATA.localStorage.default[i]);
500 // ----------------------------------------------------------------------------------------------------------------
502 if(NETDATA.options.debug.main_loop === true)
503 console.log('welcome to NETDATA');
505 NETDATA.onresize = function() {
506 NETDATA.options.last_resized = Date.now();
510 NETDATA.onscroll_updater_count = 0;
511 NETDATA.onscroll_updater_running = false;
512 NETDATA.onscroll_updater_last_run = 0;
513 NETDATA.onscroll_updater_watchdog = null;
514 NETDATA.onscroll_updater_max_duration = 0;
515 NETDATA.onscroll_updater_above_threshold_count = 0;
516 NETDATA.onscroll_updater = function() {
517 NETDATA.onscroll_updater_running = true;
518 NETDATA.onscroll_updater_count++;
519 var start = Date.now();
521 var targets = NETDATA.options.targets;
522 var len = targets.length;
524 // when the user scrolls he sees that we have
525 // hidden all the not-visible charts
526 // using this little function we try to switch
527 // the charts back to visible quickly
530 if(NETDATA.options.abort_ajax_on_scroll === true) {
531 // we have to cancel pending requests too
534 if (targets[len]._updating === true) {
535 if (typeof targets[len].xhr !== 'undefined') {
536 targets[len].xhr.abort();
537 targets[len].running = false;
538 targets[len]._updating = false;
540 targets[len].isVisible();
545 // just find which chart is visible
548 targets[len].isVisible();
551 var end = Date.now();
552 // console.log('scroll No ' + NETDATA.onscroll_updater_count + ' calculation took ' + (end - start).toString() + ' ms');
554 if(NETDATA.options.current.async_on_scroll === false) {
555 var dt = end - start;
556 if(dt > NETDATA.onscroll_updater_max_duration) {
557 // console.log('max onscroll event handler duration increased to ' + dt);
558 NETDATA.onscroll_updater_max_duration = dt;
561 if(dt > NETDATA.options.current.onscroll_worker_duration_threshold) {
562 // console.log('slow: ' + dt);
563 NETDATA.onscroll_updater_above_threshold_count++;
565 if(NETDATA.onscroll_updater_above_threshold_count > 2 && NETDATA.onscroll_updater_above_threshold_count * 100 / NETDATA.onscroll_updater_count > 2) {
566 NETDATA.setOption('async_on_scroll', true);
567 console.log('NETDATA: your browser is slow - enabling asynchronous onscroll event handler.');
572 NETDATA.onscroll_updater_last_run = start;
573 NETDATA.onscroll_updater_running = false;
576 NETDATA.onscroll = function() {
577 // console.log('onscroll');
579 NETDATA.options.last_page_scroll = Date.now();
580 NETDATA.options.auto_refresher_stop_until = 0;
582 if(NETDATA.options.targets === null) return;
584 if(NETDATA.options.current.async_on_scroll === true) {
586 if(NETDATA.onscroll_updater_running === false) {
587 NETDATA.onscroll_updater_running = true;
588 setTimeout(NETDATA.onscroll_updater, 0);
591 if(NETDATA.onscroll_updater_watchdog !== null)
592 clearTimeout(NETDATA.onscroll_updater_watchdog);
594 NETDATA.onscroll_updater_watchdog = setTimeout(function() {
595 if(NETDATA.onscroll_updater_running === false && NETDATA.options.last_page_scroll > NETDATA.onscroll_updater_last_run) {
596 // console.log('watchdog');
597 NETDATA.onscroll_updater();
600 NETDATA.onscroll_updater_watchdog = null;
606 NETDATA.onscroll_updater();
610 window.onresize = NETDATA.onresize;
611 window.onscroll = NETDATA.onscroll;
613 // ----------------------------------------------------------------------------------------------------------------
616 NETDATA.errorCodes = {
617 100: { message: "Cannot load chart library", alert: true },
618 101: { message: "Cannot load jQuery", alert: true },
619 402: { message: "Chart library not found", alert: false },
620 403: { message: "Chart library not enabled/is failed", alert: false },
621 404: { message: "Chart not found", alert: false },
622 405: { message: "Cannot download charts index from server", alert: true },
623 406: { message: "Invalid charts index downloaded from server", alert: true },
624 407: { message: "Cannot HELLO netdata server", alert: false },
625 408: { message: "Netdata servers sent invalid response to HELLO", alert: false },
626 409: { message: "Cannot ACCESS netdata registry", alert: false },
627 410: { message: "Netdata registry ACCESS failed", alert: false },
628 411: { message: "Netdata registry server send invalid response to DELETE ", alert: false },
629 412: { message: "Netdata registry DELETE failed", alert: false },
630 413: { message: "Netdata registry server send invalid response to SWITCH ", alert: false },
631 414: { message: "Netdata registry SWITCH failed", alert: false },
632 415: { message: "Netdata alarms download failed", alert: false },
633 416: { message: "Netdata alarms log download failed", alert: false },
634 417: { message: "Netdata registry server send invalid response to SEARCH ", alert: false },
635 418: { message: "Netdata registry SEARCH failed", alert: false }
637 NETDATA.errorLast = {
643 NETDATA.error = function(code, msg) {
644 NETDATA.errorLast.code = code;
645 NETDATA.errorLast.message = msg;
646 NETDATA.errorLast.datetime = Date.now();
648 console.log("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
651 if(typeof netdataErrorCallback === 'function') {
652 ret = netdataErrorCallback('system', code, msg);
655 if(ret && NETDATA.errorCodes[code].alert)
656 alert("ERROR " + code + ": " + NETDATA.errorCodes[code].message + ": " + msg);
659 NETDATA.errorReset = function() {
660 NETDATA.errorLast.code = 0;
661 NETDATA.errorLast.message = "You are doing fine!";
662 NETDATA.errorLast.datetime = 0;
665 // ----------------------------------------------------------------------------------------------------------------
666 // commonMin & commonMax
668 NETDATA.commonMin = {
672 get: function(state) {
673 if(typeof state.__commonMin === 'undefined') {
674 // get the commonMin setting
675 var self = $(state.element);
676 state.__commonMin = self.data('common-min') || null;
679 var min = state.data.min;
680 var name = state.__commonMin;
683 // we don't need commonMin
684 //state.log('no need for commonMin');
688 var t = this.keys[name];
689 if(typeof t === 'undefined') {
691 this.keys[name] = {};
695 var uuid = state.uuid;
696 if(typeof t[uuid] !== 'undefined') {
697 if(t[uuid] === min) {
698 //state.log('commonMin ' + state.__commonMin + ' not changed: ' + this.latest[name]);
699 return this.latest[name];
701 else if(min < this.latest[name]) {
702 //state.log('commonMin ' + state.__commonMin + ' increased: ' + min);
704 this.latest[name] = min;
712 // find the common min
715 if(t[i] < m) m = t[i];
717 //state.log('commonMin ' + state.__commonMin + ' updated: ' + m);
718 this.latest[name] = m;
723 NETDATA.commonMax = {
727 get: function(state) {
728 if(typeof state.__commonMax === 'undefined') {
729 // get the commonMax setting
730 var self = $(state.element);
731 state.__commonMax = self.data('common-max') || null;
734 var max = state.data.max;
735 var name = state.__commonMax;
738 // we don't need commonMax
739 //state.log('no need for commonMax');
743 var t = this.keys[name];
744 if(typeof t === 'undefined') {
746 this.keys[name] = {};
750 var uuid = state.uuid;
751 if(typeof t[uuid] !== 'undefined') {
752 if(t[uuid] === max) {
753 //state.log('commonMax ' + state.__commonMax + ' not changed: ' + this.latest[name]);
754 return this.latest[name];
756 else if(max > this.latest[name]) {
757 //state.log('commonMax ' + state.__commonMax + ' increased: ' + max);
759 this.latest[name] = max;
767 // find the common max
770 if(t[i] > m) m = t[i];
772 //state.log('commonMax ' + state.__commonMax + ' updated: ' + m);
773 this.latest[name] = m;
778 // ----------------------------------------------------------------------------------------------------------------
781 // When multiple charts need the same chart, we avoid downloading it
782 // multiple times (and having it in browser memory multiple time)
783 // by using this registry.
785 // Every time we download a chart definition, we save it here with .add()
786 // Then we try to get it back with .get(). If that fails, we download it.
788 NETDATA.chartRegistry = {
791 fixid: function(id) {
792 return id.replace(/:/g, "_").replace(/\//g, "_");
795 add: function(host, id, data) {
796 host = this.fixid(host);
799 if(typeof this.charts[host] === 'undefined')
800 this.charts[host] = {};
802 //console.log('added ' + host + '/' + id);
803 this.charts[host][id] = data;
806 get: function(host, id) {
807 host = this.fixid(host);
810 if(typeof this.charts[host] === 'undefined')
813 if(typeof this.charts[host][id] === 'undefined')
816 //console.log('cached ' + host + '/' + id);
817 return this.charts[host][id];
820 downloadAll: function(host, callback) {
821 while(host.slice(-1) === '/')
822 host = host.substring(0, host.length - 1);
827 url: host + '/api/v1/charts',
830 xhrFields: { withCredentials: true } // required for the cookie
832 .done(function(data) {
834 var h = NETDATA.chartRegistry.fixid(host);
835 self.charts[h] = data.charts;
837 else NETDATA.error(406, host + '/api/v1/charts');
839 if(typeof callback === 'function')
843 NETDATA.error(405, host + '/api/v1/charts');
845 if(typeof callback === 'function')
851 // ----------------------------------------------------------------------------------------------------------------
852 // Global Pan and Zoom on charts
854 // Using this structure are synchronize all the charts, so that
855 // when you pan or zoom one, all others are automatically refreshed
856 // to the same timespan.
858 NETDATA.globalPanAndZoom = {
859 seq: 0, // timestamp ms
860 // every time a chart is panned or zoomed
861 // we set the timestamp here
862 // then we use it as a sequence number
863 // to find if other charts are syncronized
866 master: null, // the master chart (state), to which all others
869 force_before_ms: null, // the timespan to sync all other charts
870 force_after_ms: null,
875 setMaster: function(state, after, before) {
876 if(NETDATA.options.current.sync_pan_and_zoom === false)
879 if(this.master !== null && this.master !== state)
880 this.master.resetChart(true, true);
882 var now = Date.now();
885 this.force_after_ms = after;
886 this.force_before_ms = before;
887 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.global_pan_sync_time;
889 if(typeof this.callback === 'function')
890 this.callback(true, after, before);
894 clearMaster: function() {
895 if(this.master !== null) {
896 var st = this.master;
903 this.force_after_ms = null;
904 this.force_before_ms = null;
905 NETDATA.options.auto_refresher_stop_until = 0;
907 if(typeof this.callback === 'function')
908 this.callback(false, 0, 0);
911 // is the given state the master of the global
912 // pan and zoom sync?
913 isMaster: function(state) {
914 if(this.master === state) return true;
918 // are we currently have a global pan and zoom sync?
919 isActive: function() {
920 if(this.master !== null && this.force_before_ms !== null && this.force_after_ms !== null && this.seq !== 0) return true;
924 // check if a chart, other than the master
925 // needs to be refreshed, due to the global pan and zoom
926 shouldBeAutoRefreshed: function(state) {
927 if(this.master === null || this.seq === 0)
930 //if(state.needsRecreation())
933 if(state.tm.pan_and_zoom_seq === this.seq)
940 // ----------------------------------------------------------------------------------------------------------------
941 // dimensions selection
944 // move color assignment to dimensions, here
946 dimensionStatus = function(parent, label, name_div, value_div, color) {
947 this.enabled = false;
948 this.parent = parent;
950 this.name_div = null;
951 this.value_div = null;
952 this.color = NETDATA.themes.current.foreground;
954 if(parent.unselected_count === 0)
955 this.selected = true;
957 this.selected = false;
959 this.setOptions(name_div, value_div, color);
962 dimensionStatus.prototype.invalidate = function() {
963 this.name_div = null;
964 this.value_div = null;
965 this.enabled = false;
968 dimensionStatus.prototype.setOptions = function(name_div, value_div, color) {
971 if(this.name_div != name_div) {
972 this.name_div = name_div;
973 this.name_div.title = this.label;
974 this.name_div.style.color = this.color;
975 if(this.selected === false)
976 this.name_div.className = 'netdata-legend-name not-selected';
978 this.name_div.className = 'netdata-legend-name selected';
981 if(this.value_div != value_div) {
982 this.value_div = value_div;
983 this.value_div.title = this.label;
984 this.value_div.style.color = this.color;
985 if(this.selected === false)
986 this.value_div.className = 'netdata-legend-value not-selected';
988 this.value_div.className = 'netdata-legend-value selected';
995 dimensionStatus.prototype.setHandler = function() {
996 if(this.enabled === false) return;
1000 // this.name_div.onmousedown = this.value_div.onmousedown = function(e) {
1001 this.name_div.onclick = this.value_div.onclick = function(e) {
1003 if(ds.isSelected()) {
1005 if(e.shiftKey === true || e.ctrlKey === true) {
1006 // control or shift key is pressed -> unselect this (except is none will remain selected, in which case select all)
1009 if(ds.parent.countSelected() === 0)
1010 ds.parent.selectAll();
1013 // no key is pressed -> select only this (except if it is the only selected already, in which case select all)
1014 if(ds.parent.countSelected() === 1) {
1015 ds.parent.selectAll();
1018 ds.parent.selectNone();
1024 // this is not selected
1025 if(e.shiftKey === true || e.ctrlKey === true) {
1026 // control or shift key is pressed -> select this too
1030 // no key is pressed -> select only this
1031 ds.parent.selectNone();
1036 ds.parent.state.redrawChart();
1040 dimensionStatus.prototype.select = function() {
1041 if(this.enabled === false) return;
1043 this.name_div.className = 'netdata-legend-name selected';
1044 this.value_div.className = 'netdata-legend-value selected';
1045 this.selected = true;
1048 dimensionStatus.prototype.unselect = function() {
1049 if(this.enabled === false) return;
1051 this.name_div.className = 'netdata-legend-name not-selected';
1052 this.value_div.className = 'netdata-legend-value hidden';
1053 this.selected = false;
1056 dimensionStatus.prototype.isSelected = function() {
1057 return(this.enabled === true && this.selected === true);
1060 // ----------------------------------------------------------------------------------------------------------------
1062 dimensionsVisibility = function(state) {
1065 this.dimensions = {};
1066 this.selected_count = 0;
1067 this.unselected_count = 0;
1070 dimensionsVisibility.prototype.dimensionAdd = function(label, name_div, value_div, color) {
1071 if(typeof this.dimensions[label] === 'undefined') {
1073 this.dimensions[label] = new dimensionStatus(this, label, name_div, value_div, color);
1076 this.dimensions[label].setOptions(name_div, value_div, color);
1078 return this.dimensions[label];
1081 dimensionsVisibility.prototype.dimensionGet = function(label) {
1082 return this.dimensions[label];
1085 dimensionsVisibility.prototype.invalidateAll = function() {
1086 for(var d in this.dimensions)
1087 this.dimensions[d].invalidate();
1090 dimensionsVisibility.prototype.selectAll = function() {
1091 for(var d in this.dimensions)
1092 this.dimensions[d].select();
1095 dimensionsVisibility.prototype.countSelected = function() {
1097 for(var d in this.dimensions)
1098 if(this.dimensions[d].isSelected()) i++;
1103 dimensionsVisibility.prototype.selectNone = function() {
1104 for(var d in this.dimensions)
1105 this.dimensions[d].unselect();
1108 dimensionsVisibility.prototype.selected2BooleanArray = function(array) {
1109 var ret = new Array();
1110 this.selected_count = 0;
1111 this.unselected_count = 0;
1113 var len = array.length;
1115 var ds = this.dimensions[array[len]];
1116 if(typeof ds === 'undefined') {
1117 // console.log(array[i] + ' is not found');
1120 else if(ds.isSelected()) {
1122 this.selected_count++;
1126 this.unselected_count++;
1130 if(this.selected_count === 0 && this.unselected_count !== 0) {
1132 return this.selected2BooleanArray(array);
1139 // ----------------------------------------------------------------------------------------------------------------
1140 // global selection sync
1142 NETDATA.globalSelectionSync = {
1144 dont_sync_before: 0,
1149 if(this.state !== null)
1150 this.state.globalSelectionSyncStop();
1154 if(this.state !== null) {
1155 this.state.globalSelectionSyncDelay();
1160 // ----------------------------------------------------------------------------------------------------------------
1161 // Our state object, where all per-chart values are stored
1163 chartState = function(element) {
1164 var self = $(element);
1165 this.element = element;
1168 // all private functions should use 'that', instead of 'this'
1171 /* error() - private
1172 * show an error instead of the chart
1174 var error = function(msg) {
1177 if(typeof netdataErrorCallback === 'function') {
1178 ret = netdataErrorCallback('chart', that.id, msg);
1182 that.element.innerHTML = that.id + ': ' + msg;
1183 that.enabled = false;
1184 that.current = that.pan;
1188 // GUID - a unique identifier for the chart
1189 this.uuid = NETDATA.guid();
1191 // string - the name of chart
1192 this.id = self.data('netdata');
1194 // string - the key for localStorage settings
1195 this.settings_id = self.data('id') || null;
1197 // the user given dimensions of the element
1198 this.width = self.data('width') || NETDATA.chartDefaults.width;
1199 this.height = self.data('height') || NETDATA.chartDefaults.height;
1201 if(this.settings_id !== null) {
1202 this.height = NETDATA.localStorageGet('chart_heights.' + this.settings_id, this.height, function(height) {
1203 // this is the callback that will be called
1204 // if and when the user resets all localStorage variables
1205 // to their defaults
1207 resizeChartToHeight(height);
1211 // string - the netdata server URL, without any path
1212 this.host = self.data('host') || NETDATA.chartDefaults.host;
1214 // make sure the host does not end with /
1215 // all netdata API requests use absolute paths
1216 while(this.host.slice(-1) === '/')
1217 this.host = this.host.substring(0, this.host.length - 1);
1219 // string - the grouping method requested by the user
1220 this.method = self.data('method') || NETDATA.chartDefaults.method;
1222 // the time-range requested by the user
1223 this.after = self.data('after') || NETDATA.chartDefaults.after;
1224 this.before = self.data('before') || NETDATA.chartDefaults.before;
1226 // the pixels per point requested by the user
1227 this.pixels_per_point = self.data('pixels-per-point') || 1;
1228 this.points = self.data('points') || null;
1230 // the dimensions requested by the user
1231 this.dimensions = self.data('dimensions') || null;
1233 // the chart library requested by the user
1234 this.library_name = self.data('chart-library') || NETDATA.chartDefaults.library;
1236 // how many retries we have made to load chart data from the server
1237 this.retries_on_data_failures = 0;
1239 // object - the chart library used
1240 this.library = null;
1244 this.colors_assigned = {};
1245 this.colors_available = null;
1247 // the element already created by the user
1248 this.element_message = null;
1250 // the element with the chart
1251 this.element_chart = null;
1253 // the element with the legend of the chart (if created by us)
1254 this.element_legend = null;
1255 this.element_legend_childs = {
1265 this.chart_url = null; // string - the url to download chart info
1266 this.chart = null; // object - the chart as downloaded from the server
1268 this.title = self.data('title') || null; // the title of the chart
1269 this.units = self.data('units') || null; // the units of the chart dimensions
1270 this.append_options = self.data('append-options') || null; // additional options to pass to netdata
1271 this.override_options = self.data('override-options') || null; // override options to pass to netdata
1273 this.running = false; // boolean - true when the chart is being refreshed now
1274 this.validated = false; // boolean - has the chart been validated?
1275 this.enabled = true; // boolean - is the chart enabled for refresh?
1276 this.paused = false; // boolean - is the chart paused for any reason?
1277 this.selected = false; // boolean - is the chart shown a selection?
1278 this.debug = false; // boolean - console.log() debug info about this chart
1280 this.netdata_first = 0; // milliseconds - the first timestamp in netdata
1281 this.netdata_last = 0; // milliseconds - the last timestamp in netdata
1282 this.requested_after = null; // milliseconds - the timestamp of the request after param
1283 this.requested_before = null; // milliseconds - the timestamp of the request before param
1284 this.requested_padding = null;
1285 this.view_after = 0;
1286 this.view_before = 0;
1288 this.value_decimal_detail = -1;
1290 var d = self.data('decimal-digits');
1291 if(typeof d === 'number') {
1292 this.value_decimal_detail = 1;
1294 this.value_decimal_detail *= 10;
1301 force_update_at: 0, // the timestamp to force the update at
1302 force_before_ms: null,
1303 force_after_ms: null
1308 force_update_at: 0, // the timestamp to force the update at
1309 force_before_ms: null,
1310 force_after_ms: null
1315 force_update_at: 0, // the timestamp to force the update at
1316 force_before_ms: null,
1317 force_after_ms: null
1320 // this is a pointer to one of the sub-classes below
1322 this.current = this.auto;
1324 // check the requested library is available
1325 // we don't initialize it here - it will be initialized when
1326 // this chart will be first used
1327 if(typeof NETDATA.chartLibraries[that.library_name] === 'undefined') {
1328 NETDATA.error(402, that.library_name);
1329 error('chart library "' + that.library_name + '" is not found');
1332 else if(NETDATA.chartLibraries[that.library_name].enabled === false) {
1333 NETDATA.error(403, that.library_name);
1334 error('chart library "' + that.library_name + '" is not enabled');
1338 that.library = NETDATA.chartLibraries[that.library_name];
1340 // milliseconds - the time the last refresh took
1341 this.refresh_dt_ms = 0;
1343 // if we need to report the rendering speed
1344 // find the element that needs to be updated
1345 var refresh_dt_element_name = self.data('dt-element-name') || null; // string - the element to print refresh_dt_ms
1347 if(refresh_dt_element_name !== null)
1348 this.refresh_dt_element = document.getElementById(refresh_dt_element_name) || null;
1350 this.refresh_dt_element = null;
1352 this.dimensions_visibility = new dimensionsVisibility(this);
1354 this._updating = false;
1356 // ============================================================================================================
1357 // PRIVATE FUNCTIONS
1359 var createDOM = function() {
1360 if(that.enabled === false) return;
1362 if(that.element_message !== null) that.element_message.innerHTML = '';
1363 if(that.element_legend !== null) that.element_legend.innerHTML = '';
1364 if(that.element_chart !== null) that.element_chart.innerHTML = '';
1366 that.element.innerHTML = '';
1368 that.element_message = document.createElement('div');
1369 that.element_message.className = ' netdata-message hidden';
1370 that.element.appendChild(that.element_message);
1372 that.element_chart = document.createElement('div');
1373 that.element_chart.id = that.library_name + '-' + that.uuid + '-chart';
1374 that.element.appendChild(that.element_chart);
1376 if(that.hasLegend() === true) {
1377 that.element.className = "netdata-container-with-legend";
1378 that.element_chart.className = 'netdata-chart-with-legend-right netdata-' + that.library_name + '-chart-with-legend-right';
1380 that.element_legend = document.createElement('div');
1381 that.element_legend.className = 'netdata-chart-legend netdata-' + that.library_name + '-legend';
1382 that.element.appendChild(that.element_legend);
1385 that.element.className = "netdata-container";
1386 that.element_chart.className = ' netdata-chart netdata-' + that.library_name + '-chart';
1388 that.element_legend = null;
1390 that.element_legend_childs.series = null;
1392 if(typeof(that.width) === 'string')
1393 $(that.element).css('width', that.width);
1394 else if(typeof(that.width) === 'number')
1395 $(that.element).css('width', that.width + 'px');
1397 if(typeof(that.library.aspect_ratio) === 'undefined') {
1398 if(typeof(that.height) === 'string')
1399 $(that.element).css('height', that.height);
1400 else if(typeof(that.height) === 'number')
1401 $(that.element).css('height', that.height + 'px');
1404 var w = that.element.offsetWidth;
1405 if(w === null || w === 0) {
1406 // the div is hidden
1407 // this will resize the chart when next viewed
1408 that.tm.last_resized = 0;
1411 $(that.element).css('height', (that.element.offsetWidth * that.library.aspect_ratio / 100).toString() + 'px');
1414 if(NETDATA.chartDefaults.min_width !== null)
1415 $(that.element).css('min-width', NETDATA.chartDefaults.min_width);
1417 that.tm.last_dom_created = Date.now();
1423 * initialize state variables
1424 * destroy all (possibly) created state elements
1425 * create the basic DOM for a chart
1427 var init = function() {
1428 if(that.enabled === false) return;
1430 that.paused = false;
1431 that.selected = false;
1433 that.chart_created = false; // boolean - is the library.create() been called?
1434 that.updates_counter = 0; // numeric - the number of refreshes made so far
1435 that.updates_since_last_unhide = 0; // numeric - the number of refreshes made since the last time the chart was unhidden
1436 that.updates_since_last_creation = 0; // numeric - the number of refreshes made since the last time the chart was created
1439 last_initialized: 0, // milliseconds - the timestamp it was last initialized
1440 last_dom_created: 0, // milliseconds - the timestamp its DOM was last created
1441 last_mode_switch: 0, // milliseconds - the timestamp it switched modes
1443 last_info_downloaded: 0, // milliseconds - the timestamp we downloaded the chart
1444 last_updated: 0, // the timestamp the chart last updated with data
1445 pan_and_zoom_seq: 0, // the sequence number of the global synchronization
1447 // Used with NETDATA.globalPanAndZoom.seq
1448 last_visible_check: 0, // the time we last checked if it is visible
1449 last_resized: 0, // the time the chart was resized
1450 last_hidden: 0, // the time the chart was hidden
1451 last_unhidden: 0, // the time the chart was unhidden
1452 last_autorefreshed: 0 // the time the chart was last refreshed
1455 that.data = null; // the last data as downloaded from the netdata server
1456 that.data_url = 'invalid://'; // string - the last url used to update the chart
1457 that.data_points = 0; // number - the number of points returned from netdata
1458 that.data_after = 0; // milliseconds - the first timestamp of the data
1459 that.data_before = 0; // milliseconds - the last timestamp of the data
1460 that.data_update_every = 0; // milliseconds - the frequency to update the data
1462 that.tm.last_initialized = Date.now();
1465 that.setMode('auto');
1468 var maxMessageFontSize = function() {
1469 // normally we want a font size, as tall as the element
1470 var h = that.element_message.clientHeight;
1472 // but give it some air, 20% let's say, or 5 pixels min
1473 var lost = Math.max(h * 0.2, 5);
1476 // center the text, vertically
1477 var paddingTop = (lost - 5) / 2;
1479 // but check the width too
1480 // it should fit 10 characters in it
1481 var w = that.element_message.clientWidth / 10;
1483 paddingTop += (h - w) / 2;
1487 // and don't make it too huge
1488 // 5% of the screen size is good
1489 if(h > screen.height / 20) {
1490 paddingTop += (h - (screen.height / 20)) / 2;
1491 h = screen.height / 20;
1495 that.element_message.style.fontSize = h.toString() + 'px';
1496 that.element_message.style.paddingTop = paddingTop.toString() + 'px';
1499 var showMessage = function(msg) {
1500 that.element_message.className = 'netdata-message';
1501 that.element_message.innerHTML = msg;
1502 that.element_message.style.fontSize = 'x-small';
1503 that.element_message.style.paddingTop = '0px';
1504 that.___messageHidden___ = undefined;
1507 var showMessageIcon = function(icon) {
1508 that.element_message.innerHTML = icon;
1509 that.element_message.className = 'netdata-message icon';
1510 maxMessageFontSize();
1511 that.___messageHidden___ = undefined;
1514 var hideMessage = function() {
1515 if(typeof that.___messageHidden___ === 'undefined') {
1516 that.___messageHidden___ = true;
1517 that.element_message.className = 'netdata-message hidden';
1521 var showRendering = function() {
1523 if(that.chart !== null) {
1524 if(that.chart.chart_type === 'line')
1525 icon = '<i class="fa fa-line-chart"></i>';
1527 icon = '<i class="fa fa-area-chart"></i>';
1530 icon = '<i class="fa fa-area-chart"></i>';
1532 showMessageIcon(icon + ' netdata');
1535 var showLoading = function() {
1536 if(that.chart_created === false) {
1537 showMessageIcon('<i class="fa fa-refresh"></i> netdata');
1543 var isHidden = function() {
1544 if(typeof that.___chartIsHidden___ !== 'undefined')
1550 // hide the chart, when it is not visible - called from isVisible()
1551 var hideChart = function() {
1552 // hide it, if it is not already hidden
1553 if(isHidden() === true) return;
1555 if(that.chart_created === true) {
1556 if(NETDATA.options.current.destroy_on_hide === true) {
1557 // we should destroy it
1562 that.element_chart.style.display = 'none';
1563 if(that.element_legend !== null) that.element_legend.style.display = 'none';
1564 that.tm.last_hidden = Date.now();
1567 // This works, but I not sure there are no corner cases somewhere
1568 // so it is commented - if the user has memory issues he can
1569 // set Destroy on Hide for all charts
1570 // that.data = null;
1574 that.___chartIsHidden___ = true;
1577 // unhide the chart, when it is visible - called from isVisible()
1578 var unhideChart = function() {
1579 if(isHidden() === false) return;
1581 that.___chartIsHidden___ = undefined;
1582 that.updates_since_last_unhide = 0;
1584 if(that.chart_created === false) {
1585 // we need to re-initialize it, to show our background
1586 // logo in bootstrap tabs, until the chart loads
1590 that.tm.last_unhidden = Date.now();
1591 that.element_chart.style.display = '';
1592 if(that.element_legend !== null) that.element_legend.style.display = '';
1598 var canBeRendered = function() {
1599 if(isHidden() === true || that.isVisible(true) === false)
1605 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1606 var callChartLibraryUpdateSafely = function(data) {
1609 if(canBeRendered() === false)
1612 if(NETDATA.options.debug.chart_errors === true)
1613 status = that.library.update(that, data);
1616 status = that.library.update(that, data);
1623 if(status === false) {
1624 error('chart failed to be updated as ' + that.library_name);
1631 // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
1632 var callChartLibraryCreateSafely = function(data) {
1635 if(canBeRendered() === false)
1638 if(NETDATA.options.debug.chart_errors === true)
1639 status = that.library.create(that, data);
1642 status = that.library.create(that, data);
1649 if(status === false) {
1650 error('chart failed to be created as ' + that.library_name);
1654 that.chart_created = true;
1655 that.updates_since_last_creation = 0;
1659 // ----------------------------------------------------------------------------------------------------------------
1662 // resizeChart() - private
1663 // to be called just before the chart library to make sure that
1664 // a properly sized dom is available
1665 var resizeChart = function() {
1666 if(that.isVisible() === true && that.tm.last_resized < NETDATA.options.last_resized) {
1667 if(that.chart_created === false) return;
1669 if(that.needsRecreation()) {
1672 else if(typeof that.library.resize === 'function') {
1673 that.library.resize(that);
1675 if(that.element_legend_childs.nano !== null && that.element_legend_childs.nano_options !== null)
1676 $(that.element_legend_childs.nano).nanoScroller();
1678 maxMessageFontSize();
1681 that.tm.last_resized = Date.now();
1685 // this is the actual chart resize algorithm
1687 // - resize the entire container
1688 // - update the internal states
1689 // - resize the chart as the div changes height
1690 // - update the scrollbar of the legend
1691 var resizeChartToHeight = function(h) {
1693 that.element.style.height = h;
1695 if(that.settings_id !== null)
1696 NETDATA.localStorageSet('chart_heights.' + that.settings_id, h);
1698 var now = Date.now();
1699 NETDATA.options.last_page_scroll = now;
1700 NETDATA.options.auto_refresher_stop_until = now + NETDATA.options.current.stop_updates_while_resizing;
1703 that.tm.last_resized = 0;
1707 this.resizeHandler = function(e) {
1710 if(typeof this.event_resize === 'undefined'
1711 || this.event_resize.chart_original_w === 'undefined'
1712 || this.event_resize.chart_original_h === 'undefined')
1713 this.event_resize = {
1714 chart_original_w: this.element.clientWidth,
1715 chart_original_h: this.element.clientHeight,
1719 if(e.type === 'touchstart') {
1720 this.event_resize.mouse_start_x = e.touches.item(0).pageX;
1721 this.event_resize.mouse_start_y = e.touches.item(0).pageY;
1724 this.event_resize.mouse_start_x = e.clientX;
1725 this.event_resize.mouse_start_y = e.clientY;
1728 this.event_resize.chart_start_w = this.element.clientWidth;
1729 this.event_resize.chart_start_h = this.element.clientHeight;
1730 this.event_resize.chart_last_w = this.element.clientWidth;
1731 this.event_resize.chart_last_h = this.element.clientHeight;
1733 var now = Date.now();
1734 if(now - this.event_resize.last <= NETDATA.options.current.double_click_speed) {
1735 // double click / double tap event
1737 // the optimal height of the chart
1738 // showing the entire legend
1739 var optimal = this.event_resize.chart_last_h
1740 + this.element_legend_childs.content.scrollHeight
1741 - this.element_legend_childs.content.clientHeight;
1743 // if we are not optimal, be optimal
1744 if(this.event_resize.chart_last_h != optimal)
1745 resizeChartToHeight(optimal.toString() + 'px');
1747 // else if we do not have the original height
1748 // reset to the original height
1749 else if(this.event_resize.chart_last_h != this.event_resize.chart_original_h)
1750 resizeChartToHeight(this.event_resize.chart_original_h.toString() + 'px');
1753 this.event_resize.last = now;
1755 // process movement event
1756 document.onmousemove =
1757 document.ontouchmove =
1758 this.element_legend_childs.resize_handler.onmousemove =
1759 this.element_legend_childs.resize_handler.ontouchmove =
1764 case 'mousemove': y = e.clientY; break;
1765 case 'touchmove': y = e.touches.item(e.touches - 1).pageY; break;
1769 var newH = that.event_resize.chart_start_h + y - that.event_resize.mouse_start_y;
1771 if(newH >= 70 && newH !== that.event_resize.chart_last_h) {
1772 resizeChartToHeight(newH.toString() + 'px');
1773 that.event_resize.chart_last_h = newH;
1778 // process end event
1779 document.onmouseup =
1780 document.ontouchend =
1781 this.element_legend_childs.resize_handler.onmouseup =
1782 this.element_legend_childs.resize_handler.ontouchend =
1784 // remove all the hooks
1785 document.onmouseup =
1786 document.onmousemove =
1787 document.ontouchmove =
1788 document.ontouchend =
1789 that.element_legend_childs.resize_handler.onmousemove =
1790 that.element_legend_childs.resize_handler.ontouchmove =
1791 that.element_legend_childs.resize_handler.onmouseout =
1792 that.element_legend_childs.resize_handler.onmouseup =
1793 that.element_legend_childs.resize_handler.ontouchend =
1796 // allow auto-refreshes
1797 NETDATA.options.auto_refresher_stop_until = 0;
1803 var noDataToShow = function() {
1804 showMessageIcon('<i class="fa fa-warning"></i> empty');
1805 that.legendUpdateDOM();
1806 that.tm.last_autorefreshed = Date.now();
1807 // that.data_update_every = 30 * 1000;
1808 //that.element_chart.style.display = 'none';
1809 //if(that.element_legend !== null) that.element_legend.style.display = 'none';
1810 //that.___chartIsHidden___ = true;
1813 // ============================================================================================================
1816 this.error = function(msg) {
1820 this.setMode = function(m) {
1821 if(this.current !== null && this.current.name === m) return;
1824 this.current = this.auto;
1825 else if(m === 'pan')
1826 this.current = this.pan;
1827 else if(m === 'zoom')
1828 this.current = this.zoom;
1830 this.current = this.auto;
1832 this.current.force_update_at = 0;
1833 this.current.force_before_ms = null;
1834 this.current.force_after_ms = null;
1836 this.tm.last_mode_switch = Date.now();
1839 // ----------------------------------------------------------------------------------------------------------------
1840 // global selection sync
1842 // prevent to global selection sync for some time
1843 this.globalSelectionSyncDelay = function(ms) {
1844 if(NETDATA.options.current.sync_selection === false)
1847 if(typeof ms === 'number')
1848 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + ms;
1850 NETDATA.globalSelectionSync.dont_sync_before = Date.now() + NETDATA.options.current.sync_selection_delay;
1853 // can we globally apply selection sync?
1854 this.globalSelectionSyncAbility = function() {
1855 if(NETDATA.options.current.sync_selection === false)
1858 if(NETDATA.globalSelectionSync.dont_sync_before > Date.now())
1864 this.globalSelectionSyncIsMaster = function() {
1865 if(NETDATA.globalSelectionSync.state === this)
1871 // this chart is the master of the global selection sync
1872 this.globalSelectionSyncBeMaster = function() {
1874 if(this.globalSelectionSyncIsMaster()) {
1875 if(this.debug === true)
1876 this.log('sync: I am the master already.');
1881 if(NETDATA.globalSelectionSync.state) {
1882 if(this.debug === true)
1883 this.log('sync: I am not the sync master. Resetting global sync.');
1885 this.globalSelectionSyncStop();
1888 // become the master
1889 if(this.debug === true)
1890 this.log('sync: becoming sync master.');
1892 this.selected = true;
1893 NETDATA.globalSelectionSync.state = this;
1895 // find the all slaves
1896 var targets = NETDATA.options.targets;
1897 var len = targets.length;
1902 if(this.debug === true)
1903 st.log('sync: not adding me to sync');
1905 else if(st.globalSelectionSyncIsEligible()) {
1906 if(this.debug === true)
1907 st.log('sync: adding to sync as slave');
1909 st.globalSelectionSyncBeSlave();
1913 // this.globalSelectionSyncDelay(100);
1916 // can the chart participate to the global selection sync as a slave?
1917 this.globalSelectionSyncIsEligible = function() {
1918 if(this.enabled === true
1919 && this.library !== null
1920 && typeof this.library.setSelection === 'function'
1921 && this.isVisible() === true
1922 && this.chart_created === true)
1928 // this chart becomes a slave of the global selection sync
1929 this.globalSelectionSyncBeSlave = function() {
1930 if(NETDATA.globalSelectionSync.state !== this)
1931 NETDATA.globalSelectionSync.slaves.push(this);
1934 // sync all the visible charts to the given time
1935 // this is to be called from the chart libraries
1936 this.globalSelectionSync = function(t) {
1937 if(this.globalSelectionSyncAbility() === false) {
1938 if(this.debug === true)
1939 this.log('sync: cannot sync (yet?).');
1944 if(this.globalSelectionSyncIsMaster() === false) {
1945 if(this.debug === true)
1946 this.log('sync: trying to be sync master.');
1948 this.globalSelectionSyncBeMaster();
1950 if(this.globalSelectionSyncAbility() === false) {
1951 if(this.debug === true)
1952 this.log('sync: cannot sync (yet?).');
1958 NETDATA.globalSelectionSync.last_t = t;
1959 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1964 // stop syncing all charts to the given time
1965 this.globalSelectionSyncStop = function() {
1966 if(NETDATA.globalSelectionSync.slaves.length) {
1967 if(this.debug === true)
1968 this.log('sync: cleaning up...');
1970 $.each(NETDATA.globalSelectionSync.slaves, function(i, st) {
1972 if(that.debug === true)
1973 st.log('sync: not adding me to sync stop');
1976 if(that.debug === true)
1977 st.log('sync: removed slave from sync');
1979 st.clearSelection();
1983 NETDATA.globalSelectionSync.last_t = 0;
1984 NETDATA.globalSelectionSync.slaves = [];
1985 NETDATA.globalSelectionSync.state = null;
1988 this.clearSelection();
1991 this.setSelection = function(t) {
1992 if(typeof this.library.setSelection === 'function') {
1993 if(this.library.setSelection(this, t) === true)
1994 this.selected = true;
1996 this.selected = false;
1998 else this.selected = true;
2000 if(this.selected === true && this.debug === true)
2001 this.log('selection set to ' + t.toString());
2003 return this.selected;
2006 this.clearSelection = function() {
2007 if(this.selected === true) {
2008 if(typeof this.library.clearSelection === 'function') {
2009 if(this.library.clearSelection(this) === true)
2010 this.selected = false;
2012 this.selected = true;
2014 else this.selected = false;
2016 if(this.selected === false && this.debug === true)
2017 this.log('selection cleared');
2022 return this.selected;
2025 // find if a timestamp (ms) is shown in the current chart
2026 this.timeIsVisible = function(t) {
2027 if(t >= this.data_after && t <= this.data_before)
2032 this.calculateRowForTime = function(t) {
2033 if(this.timeIsVisible(t) === false) return -1;
2034 return Math.floor((t - this.data_after) / this.data_update_every);
2037 // ----------------------------------------------------------------------------------------------------------------
2040 this.log = function(msg) {
2041 console.log(this.id + ' (' + this.library_name + ' ' + this.uuid + '): ' + msg);
2044 this.pauseChart = function() {
2045 if(this.paused === false) {
2046 if(this.debug === true)
2047 this.log('pauseChart()');
2053 this.unpauseChart = function() {
2054 if(this.paused === true) {
2055 if(this.debug === true)
2056 this.log('unpauseChart()');
2058 this.paused = false;
2062 this.resetChart = function(dont_clear_master, dont_update) {
2063 if(this.debug === true)
2064 this.log('resetChart(' + dont_clear_master + ', ' + dont_update + ') called');
2066 if(typeof dont_clear_master === 'undefined')
2067 dont_clear_master = false;
2069 if(typeof dont_update === 'undefined')
2070 dont_update = false;
2072 if(dont_clear_master !== true && NETDATA.globalPanAndZoom.isMaster(this) === true) {
2073 if(this.debug === true)
2074 this.log('resetChart() diverting to clearMaster().');
2075 // this will call us back with master === true
2076 NETDATA.globalPanAndZoom.clearMaster();
2080 this.clearSelection();
2082 this.tm.pan_and_zoom_seq = 0;
2084 this.setMode('auto');
2085 this.current.force_update_at = 0;
2086 this.current.force_before_ms = null;
2087 this.current.force_after_ms = null;
2088 this.tm.last_autorefreshed = 0;
2089 this.paused = false;
2090 this.selected = false;
2091 this.enabled = true;
2092 // this.debug = false;
2094 // do not update the chart here
2095 // or the chart will flip-flop when it is the master
2096 // of a selection sync and another chart becomes
2099 if(dont_update !== true && this.isVisible() === true) {
2104 this.updateChartPanOrZoom = function(after, before) {
2105 var logme = 'updateChartPanOrZoom(' + after + ', ' + before + '): ';
2108 if(this.debug === true)
2111 if(before < after) {
2112 if(this.debug === true)
2113 this.log(logme + 'flipped parameters, rejecting it.');
2118 if(typeof this.fixed_min_duration === 'undefined')
2119 this.fixed_min_duration = Math.round((this.chartWidth() / 30) * this.chart.update_every * 1000);
2121 var min_duration = this.fixed_min_duration;
2122 var current_duration = Math.round(this.view_before - this.view_after);
2124 // round the numbers
2125 after = Math.round(after);
2126 before = Math.round(before);
2128 // align them to update_every
2129 // stretching them further away
2130 after -= after % this.data_update_every;
2131 before += this.data_update_every - (before % this.data_update_every);
2133 // the final wanted duration
2134 var wanted_duration = before - after;
2136 // to allow panning, accept just a point below our minimum
2137 if((current_duration - this.data_update_every) < min_duration)
2138 min_duration = current_duration - this.data_update_every;
2140 // we do it, but we adjust to minimum size and return false
2141 // when the wanted size is below the current and the minimum
2143 if(wanted_duration < current_duration && wanted_duration < min_duration) {
2144 if(this.debug === true)
2145 this.log(logme + 'too small: min_duration: ' + (min_duration / 1000).toString() + ', wanted: ' + (wanted_duration / 1000).toString());
2147 min_duration = this.fixed_min_duration;
2149 var dt = (min_duration - wanted_duration) / 2;
2152 wanted_duration = before - after;
2156 var tolerance = this.data_update_every * 2;
2157 var movement = Math.abs(before - this.view_before);
2159 if(Math.abs(current_duration - wanted_duration) <= tolerance && movement <= tolerance && ret === true) {
2160 if(this.debug === true)
2161 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);
2165 if(this.current.name === 'auto') {
2166 this.log(logme + 'caller called me with mode: ' + this.current.name);
2167 this.setMode('pan');
2170 if(this.debug === true)
2171 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);
2173 this.current.force_update_at = Date.now() + NETDATA.options.current.pan_and_zoom_delay;
2174 this.current.force_after_ms = after;
2175 this.current.force_before_ms = before;
2176 NETDATA.globalPanAndZoom.setMaster(this, after, before);
2180 this.legendFormatValue = function(value) {
2181 if(value === null || value === 'undefined') return '-';
2182 if(typeof value !== 'number') return value;
2184 if(this.value_decimal_detail !== -1)
2185 return (Math.round(value * this.value_decimal_detail) / this.value_decimal_detail).toLocaleString();
2187 var abs = Math.abs(value);
2188 if(abs >= 1000) return (Math.round(value)).toLocaleString();
2189 if(abs >= 100 ) return (Math.round(value * 10) / 10).toLocaleString();
2190 if(abs >= 1 ) return (Math.round(value * 100) / 100).toLocaleString();
2191 if(abs >= 0.1 ) return (Math.round(value * 1000) / 1000).toLocaleString();
2192 return (Math.round(value * 10000) / 10000).toLocaleString();
2195 this.legendSetLabelValue = function(label, value) {
2196 var series = this.element_legend_childs.series[label];
2197 if(typeof series === 'undefined') return;
2198 if(series.value === null && series.user === null) return;
2200 // if the value has not changed, skip DOM update
2201 //if(series.last === value) return;
2204 if(typeof value === 'number') {
2205 var v = Math.abs(value);
2206 s = r = this.legendFormatValue(value);
2208 if(typeof series.last === 'number') {
2209 if(v > series.last) s += '<i class="fa fa-angle-up" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2210 else if(v < series.last) s += '<i class="fa fa-angle-down" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2211 else s += '<i class="fa fa-angle-left" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2213 else s += '<i class="fa fa-angle-right" style="width: 8px; text-align: center; overflow: hidden; vertical-align: middle;"></i>';
2218 series.last = value;
2221 if(series.value !== null) series.value.innerHTML = s;
2222 if(series.user !== null) series.user.innerHTML = r;
2225 this.legendSetDate = function(ms) {
2226 if(typeof ms !== 'number') {
2227 this.legendShowUndefined();
2231 var d = new Date(ms);
2233 if(this.element_legend_childs.title_date)
2234 this.element_legend_childs.title_date.innerHTML = d.toLocaleDateString();
2236 if(this.element_legend_childs.title_time)
2237 this.element_legend_childs.title_time.innerHTML = d.toLocaleTimeString();
2239 if(this.element_legend_childs.title_units)
2240 this.element_legend_childs.title_units.innerHTML = this.units;
2243 this.legendShowUndefined = function() {
2244 if(this.element_legend_childs.title_date)
2245 this.element_legend_childs.title_date.innerHTML = ' ';
2247 if(this.element_legend_childs.title_time)
2248 this.element_legend_childs.title_time.innerHTML = this.chart.name;
2250 if(this.element_legend_childs.title_units)
2251 this.element_legend_childs.title_units.innerHTML = ' ';
2253 if(this.data && this.element_legend_childs.series !== null) {
2254 var labels = this.data.dimension_names;
2255 var i = labels.length;
2257 var label = labels[i];
2259 if(typeof label === 'undefined') continue;
2260 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2261 this.legendSetLabelValue(label, null);
2266 this.legendShowLatestValues = function() {
2267 if(this.chart === null) return;
2268 if(this.selected) return;
2270 if(this.data === null || this.element_legend_childs.series === null) {
2271 this.legendShowUndefined();
2275 var show_undefined = true;
2276 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every)
2277 show_undefined = false;
2279 if(show_undefined) {
2280 this.legendShowUndefined();
2284 this.legendSetDate(this.view_before);
2286 var labels = this.data.dimension_names;
2287 var i = labels.length;
2289 var label = labels[i];
2291 if(typeof label === 'undefined') continue;
2292 if(typeof this.element_legend_childs.series[label] === 'undefined') continue;
2295 this.legendSetLabelValue(label, null);
2297 this.legendSetLabelValue(label, this.data.view_latest_values[i]);
2301 this.legendReset = function() {
2302 this.legendShowLatestValues();
2305 // this should be called just ONCE per dimension per chart
2306 this._chartDimensionColor = function(label) {
2307 if(this.colors === null) this.chartColors();
2309 if(typeof this.colors_assigned[label] === 'undefined') {
2310 if(this.colors_available.length === 0) {
2311 var len = NETDATA.themes.current.colors.length;
2313 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2316 this.colors_assigned[label] = this.colors_available.shift();
2318 if(this.debug === true)
2319 this.log('label "' + label + '" got color "' + this.colors_assigned[label]);
2322 if(this.debug === true)
2323 this.log('label "' + label + '" already has color "' + this.colors_assigned[label] + '"');
2326 this.colors.push(this.colors_assigned[label]);
2327 return this.colors_assigned[label];
2330 this.chartColors = function() {
2331 if(this.colors !== null) return this.colors;
2333 this.colors = new Array();
2334 this.colors_available = new Array();
2336 // add the standard colors
2337 var len = NETDATA.themes.current.colors.length;
2339 this.colors_available.unshift(NETDATA.themes.current.colors[len]);
2341 // add the user supplied colors
2342 var c = $(this.element).data('colors');
2343 // this.log('read colors: ' + c);
2344 if(typeof c !== 'undefined' && c !== null && c.length > 0) {
2345 if(typeof c !== 'string') {
2346 this.log('invalid color given: ' + c + ' (give a space separated list of colors)');
2356 this.colors_available.unshift(c[len]);
2357 // this.log('adding color: ' + c[len]);
2366 this.legendUpdateDOM = function() {
2369 // check that the legend DOM is up to date for the downloaded dimensions
2370 if(typeof this.element_legend_childs.series !== 'object' || this.element_legend_childs.series === null) {
2371 // this.log('the legend does not have any series - requesting legend update');
2374 else if(this.data === null) {
2375 // this.log('the chart does not have any data - requesting legend update');
2378 else if(typeof this.element_legend_childs.series.labels_key === 'undefined') {
2382 var labels = this.data.dimension_names.toString();
2383 if(labels !== this.element_legend_childs.series.labels_key) {
2386 if(this.debug === true)
2387 this.log('NEW LABELS: "' + labels + '" NOT EQUAL OLD LABELS: "' + this.element_legend_childs.series.labels_key + '"');
2391 if(needed === false) {
2392 // make sure colors available
2395 // do we have to update the current values?
2396 // we do this, only when the visible chart is current
2397 if(Math.abs(this.netdata_last - this.view_before) <= this.data_update_every) {
2398 if(this.debug === true)
2399 this.log('chart is in latest position... updating values on legend...');
2401 //var labels = this.data.dimension_names;
2402 //var i = labels.length;
2404 // this.legendSetLabelValue(labels[i], this.data.latest_values[i]);
2408 if(this.colors === null) {
2409 // this is the first time we update the chart
2410 // let's assign colors to all dimensions
2411 if(this.library.track_colors() === true)
2412 for(var dim in this.chart.dimensions)
2413 this._chartDimensionColor(this.chart.dimensions[dim].name);
2415 // we will re-generate the colors for the chart
2416 // based on the selected dimensions
2419 if(this.debug === true)
2420 this.log('updating Legend DOM');
2422 // mark all dimensions as invalid
2423 this.dimensions_visibility.invalidateAll();
2425 var genLabel = function(state, parent, dim, name, count) {
2426 var color = state._chartDimensionColor(name);
2428 var user_element = null;
2429 var user_id = self.data('show-value-of-' + name.toLowerCase() + '-at') || null;
2430 if(user_id === null)
2431 user_id = self.data('show-value-of-' + dim.toLowerCase() + '-at') || null;
2432 if(user_id !== null) {
2433 user_element = document.getElementById(user_id) || null;
2434 if (user_element === null)
2435 state.log('Cannot find element with id: ' + user_id);
2438 state.element_legend_childs.series[name] = {
2439 name: document.createElement('span'),
2440 value: document.createElement('span'),
2445 var label = state.element_legend_childs.series[name];
2447 // create the dimension visibility tracking for this label
2448 state.dimensions_visibility.dimensionAdd(name, label.name, label.value, color);
2450 var rgb = NETDATA.colorHex2Rgb(color);
2451 label.name.innerHTML = '<table class="netdata-legend-name-table-'
2452 + state.chart.chart_type
2453 + '" style="background-color: '
2454 + 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + NETDATA.options.current['color_fill_opacity_' + state.chart.chart_type] + ')'
2455 + '"><tr class="netdata-legend-name-tr"><td class="netdata-legend-name-td"></td></tr></table>'
2457 var text = document.createTextNode(' ' + name);
2458 label.name.appendChild(text);
2461 parent.appendChild(document.createElement('br'));
2463 parent.appendChild(label.name);
2464 parent.appendChild(label.value);
2467 var content = document.createElement('div');
2469 if(this.hasLegend()) {
2470 this.element_legend_childs = {
2472 resize_handler: document.createElement('div'),
2473 toolbox: document.createElement('div'),
2474 toolbox_left: document.createElement('div'),
2475 toolbox_right: document.createElement('div'),
2476 toolbox_reset: document.createElement('div'),
2477 toolbox_zoomin: document.createElement('div'),
2478 toolbox_zoomout: document.createElement('div'),
2479 toolbox_volume: document.createElement('div'),
2480 title_date: document.createElement('span'),
2481 title_time: document.createElement('span'),
2482 title_units: document.createElement('span'),
2483 nano: document.createElement('div'),
2485 paneClass: 'netdata-legend-series-pane',
2486 sliderClass: 'netdata-legend-series-slider',
2487 contentClass: 'netdata-legend-series-content',
2488 enabledClass: '__enabled',
2489 flashedClass: '__flashed',
2490 activeClass: '__active',
2492 alwaysVisible: true,
2498 this.element_legend.innerHTML = '';
2500 if(this.library.toolboxPanAndZoom !== null) {
2502 function get_pan_and_zoom_step(event) {
2504 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_control;
2506 else if (event.shiftKey)
2507 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_shift;
2509 else if (event.altKey)
2510 return NETDATA.options.current.pan_and_zoom_factor * NETDATA.options.current.pan_and_zoom_factor_multiplier_alt;
2513 return NETDATA.options.current.pan_and_zoom_factor;
2516 this.element_legend_childs.toolbox.className += ' netdata-legend-toolbox';
2517 this.element.appendChild(this.element_legend_childs.toolbox);
2519 this.element_legend_childs.toolbox_left.className += ' netdata-legend-toolbox-button';
2520 this.element_legend_childs.toolbox_left.innerHTML = '<i class="fa fa-backward"></i>';
2521 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_left);
2522 this.element_legend_childs.toolbox_left.onclick = function(e) {
2525 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2526 var before = that.view_before - step;
2527 var after = that.view_after - step;
2528 if(after >= that.netdata_first)
2529 that.library.toolboxPanAndZoom(that, after, before);
2531 if(NETDATA.options.current.show_help === true)
2532 $(this.element_legend_childs.toolbox_left).popover({
2537 placement: 'bottom',
2538 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2540 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>'
2544 this.element_legend_childs.toolbox_reset.className += ' netdata-legend-toolbox-button';
2545 this.element_legend_childs.toolbox_reset.innerHTML = '<i class="fa fa-play"></i>';
2546 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_reset);
2547 this.element_legend_childs.toolbox_reset.onclick = function(e) {
2549 NETDATA.resetAllCharts(that);
2551 if(NETDATA.options.current.show_help === true)
2552 $(this.element_legend_childs.toolbox_reset).popover({
2557 placement: 'bottom',
2558 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2559 title: 'Chart Reset',
2560 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>'
2563 this.element_legend_childs.toolbox_right.className += ' netdata-legend-toolbox-button';
2564 this.element_legend_childs.toolbox_right.innerHTML = '<i class="fa fa-forward"></i>';
2565 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_right);
2566 this.element_legend_childs.toolbox_right.onclick = function(e) {
2568 var step = (that.view_before - that.view_after) * get_pan_and_zoom_step(e);
2569 var before = that.view_before + step;
2570 var after = that.view_after + step;
2571 if(before <= that.netdata_last)
2572 that.library.toolboxPanAndZoom(that, after, before);
2574 if(NETDATA.options.current.show_help === true)
2575 $(this.element_legend_childs.toolbox_right).popover({
2580 placement: 'bottom',
2581 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2583 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>'
2587 this.element_legend_childs.toolbox_zoomin.className += ' netdata-legend-toolbox-button';
2588 this.element_legend_childs.toolbox_zoomin.innerHTML = '<i class="fa fa-plus"></i>';
2589 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomin);
2590 this.element_legend_childs.toolbox_zoomin.onclick = function(e) {
2592 var dt = ((that.view_before - that.view_after) * (get_pan_and_zoom_step(e) * 0.8) / 2);
2593 var before = that.view_before - dt;
2594 var after = that.view_after + dt;
2595 that.library.toolboxPanAndZoom(that, after, before);
2597 if(NETDATA.options.current.show_help === true)
2598 $(this.element_legend_childs.toolbox_zoomin).popover({
2603 placement: 'bottom',
2604 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2605 title: 'Chart Zoom In',
2606 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>'
2609 this.element_legend_childs.toolbox_zoomout.className += ' netdata-legend-toolbox-button';
2610 this.element_legend_childs.toolbox_zoomout.innerHTML = '<i class="fa fa-minus"></i>';
2611 this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_zoomout);
2612 this.element_legend_childs.toolbox_zoomout.onclick = function(e) {
2614 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);
2615 var before = that.view_before + dt;
2616 var after = that.view_after - dt;
2618 that.library.toolboxPanAndZoom(that, after, before);
2620 if(NETDATA.options.current.show_help === true)
2621 $(this.element_legend_childs.toolbox_zoomout).popover({
2626 placement: 'bottom',
2627 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2628 title: 'Chart Zoom Out',
2629 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>'
2632 //this.element_legend_childs.toolbox_volume.className += ' netdata-legend-toolbox-button';
2633 //this.element_legend_childs.toolbox_volume.innerHTML = '<i class="fa fa-sort-amount-desc"></i>';
2634 //this.element_legend_childs.toolbox_volume.title = 'Visible Volume';
2635 //this.element_legend_childs.toolbox.appendChild(this.element_legend_childs.toolbox_volume);
2636 //this.element_legend_childs.toolbox_volume.onclick = function(e) {
2637 //e.preventDefault();
2638 //alert('clicked toolbox_volume on ' + that.id);
2642 this.element_legend_childs.toolbox = null;
2643 this.element_legend_childs.toolbox_left = null;
2644 this.element_legend_childs.toolbox_reset = null;
2645 this.element_legend_childs.toolbox_right = null;
2646 this.element_legend_childs.toolbox_zoomin = null;
2647 this.element_legend_childs.toolbox_zoomout = null;
2648 this.element_legend_childs.toolbox_volume = null;
2651 this.element_legend_childs.resize_handler.className += " netdata-legend-resize-handler";
2652 this.element_legend_childs.resize_handler.innerHTML = '<i class="fa fa-chevron-up"></i><i class="fa fa-chevron-down"></i>';
2653 this.element.appendChild(this.element_legend_childs.resize_handler);
2654 if(NETDATA.options.current.show_help === true)
2655 $(this.element_legend_childs.resize_handler).popover({
2660 placement: 'bottom',
2661 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2662 title: 'Chart Resize',
2663 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>'
2667 this.element_legend_childs.resize_handler.onmousedown =
2669 that.resizeHandler(e);
2673 this.element_legend_childs.resize_handler.addEventListener('touchstart', function(e) {
2674 that.resizeHandler(e);
2677 this.element_legend_childs.title_date.className += " netdata-legend-title-date";
2678 this.element_legend.appendChild(this.element_legend_childs.title_date);
2680 this.element_legend.appendChild(document.createElement('br'));
2682 this.element_legend_childs.title_time.className += " netdata-legend-title-time";
2683 this.element_legend.appendChild(this.element_legend_childs.title_time);
2685 this.element_legend.appendChild(document.createElement('br'));
2687 this.element_legend_childs.title_units.className += " netdata-legend-title-units";
2688 this.element_legend.appendChild(this.element_legend_childs.title_units);
2690 this.element_legend.appendChild(document.createElement('br'));
2692 this.element_legend_childs.nano.className = 'netdata-legend-series';
2693 this.element_legend.appendChild(this.element_legend_childs.nano);
2695 content.className = 'netdata-legend-series-content';
2696 this.element_legend_childs.nano.appendChild(content);
2698 if(NETDATA.options.current.show_help === true)
2699 $(content).popover({
2704 placement: 'bottom',
2705 title: 'Chart Legend',
2706 delay: { show: NETDATA.options.current.show_help_delay_show_ms, hide: NETDATA.options.current.show_help_delay_hide_ms },
2707 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>'
2711 this.element_legend_childs = {
2713 resize_handler: null,
2716 toolbox_right: null,
2717 toolbox_reset: null,
2718 toolbox_zoomin: null,
2719 toolbox_zoomout: null,
2720 toolbox_volume: null,
2731 this.element_legend_childs.series.labels_key = this.data.dimension_names.toString();
2732 if(this.debug === true)
2733 this.log('labels from data: "' + this.element_legend_childs.series.labels_key + '"');
2735 for(var i = 0, len = this.data.dimension_names.length; i < len ;i++) {
2736 genLabel(this, content, this.data.dimension_ids[i], this.data.dimension_names[i], i);
2740 var tmp = new Array();
2741 for(var dim in this.chart.dimensions) {
2742 tmp.push(this.chart.dimensions[dim].name);
2743 genLabel(this, content, dim, this.chart.dimensions[dim].name, i);
2745 this.element_legend_childs.series.labels_key = tmp.toString();
2746 if(this.debug === true)
2747 this.log('labels from chart: "' + this.element_legend_childs.series.labels_key + '"');
2750 // create a hidden div to be used for hidding
2751 // the original legend of the chart library
2752 var el = document.createElement('div');
2753 if(this.element_legend !== null)
2754 this.element_legend.appendChild(el);
2755 el.style.display = 'none';
2757 this.element_legend_childs.hidden = document.createElement('div');
2758 el.appendChild(this.element_legend_childs.hidden);
2760 if(this.element_legend_childs.nano !== null && this.element_legend_childs.nano_options !== null)
2761 $(this.element_legend_childs.nano).nanoScroller(this.element_legend_childs.nano_options);
2763 this.legendShowLatestValues();
2766 this.hasLegend = function() {
2767 if(typeof this.___hasLegendCache___ !== 'undefined')
2768 return this.___hasLegendCache___;
2771 if(this.library && this.library.legend(this) === 'right-side') {
2772 var legend = $(this.element).data('legend') || 'yes';
2773 if(legend === 'yes') leg = true;
2776 this.___hasLegendCache___ = leg;
2780 this.legendWidth = function() {
2781 return (this.hasLegend())?140:0;
2784 this.legendHeight = function() {
2785 return $(this.element).height();
2788 this.chartWidth = function() {
2789 return $(this.element).width() - this.legendWidth();
2792 this.chartHeight = function() {
2793 return $(this.element).height();
2796 this.chartPixelsPerPoint = function() {
2797 // force an options provided detail
2798 var px = this.pixels_per_point;
2800 if(this.library && px < this.library.pixels_per_point(this))
2801 px = this.library.pixels_per_point(this);
2803 if(px < NETDATA.options.current.pixels_per_point)
2804 px = NETDATA.options.current.pixels_per_point;
2809 this.needsRecreation = function() {
2811 this.chart_created === true
2813 && this.library.autoresize() === false
2814 && this.tm.last_resized < NETDATA.options.last_resized
2818 this.chartURL = function() {
2819 var after, before, points_multiplier = 1;
2820 if(NETDATA.globalPanAndZoom.isActive() && NETDATA.globalPanAndZoom.isMaster(this) === false) {
2821 this.tm.pan_and_zoom_seq = NETDATA.globalPanAndZoom.seq;
2823 after = Math.round(NETDATA.globalPanAndZoom.force_after_ms / 1000);
2824 before = Math.round(NETDATA.globalPanAndZoom.force_before_ms / 1000);
2825 this.view_after = after * 1000;
2826 this.view_before = before * 1000;
2828 this.requested_padding = null;
2829 points_multiplier = 1;
2831 else if(this.current.force_before_ms !== null && this.current.force_after_ms !== null) {
2832 this.tm.pan_and_zoom_seq = 0;
2834 before = Math.round(this.current.force_before_ms / 1000);
2835 after = Math.round(this.current.force_after_ms / 1000);
2836 this.view_after = after * 1000;
2837 this.view_before = before * 1000;
2839 if(NETDATA.options.current.pan_and_zoom_data_padding === true) {
2840 this.requested_padding = Math.round((before - after) / 2);
2841 after -= this.requested_padding;
2842 before += this.requested_padding;
2843 this.requested_padding *= 1000;
2844 points_multiplier = 2;
2847 this.current.force_before_ms = null;
2848 this.current.force_after_ms = null;
2851 this.tm.pan_and_zoom_seq = 0;
2853 before = this.before;
2855 this.view_after = after * 1000;
2856 this.view_before = before * 1000;
2858 this.requested_padding = null;
2859 points_multiplier = 1;
2862 this.requested_after = after * 1000;
2863 this.requested_before = before * 1000;
2865 this.data_points = this.points || Math.round(this.chartWidth() / this.chartPixelsPerPoint());
2867 // build the data URL
2868 this.data_url = this.host + this.chart.data_url;
2869 this.data_url += "&format=" + this.library.format();
2870 this.data_url += "&points=" + (this.data_points * points_multiplier).toString();
2871 this.data_url += "&group=" + this.method;
2873 if(this.override_options !== null)
2874 this.data_url += "&options=" + this.override_options.toString();
2876 this.data_url += "&options=" + this.library.options(this);
2878 this.data_url += '|jsonwrap';
2880 if(NETDATA.options.current.eliminate_zero_dimensions === true)
2881 this.data_url += '|nonzero';
2883 if(this.append_options !== null)
2884 this.data_url += '|' + this.append_options.toString();
2887 this.data_url += "&after=" + after.toString();
2890 this.data_url += "&before=" + before.toString();
2893 this.data_url += "&dimensions=" + this.dimensions;
2895 if(NETDATA.options.debug.chart_data_url === true || this.debug === true)
2896 this.log('chartURL(): ' + this.data_url + ' WxH:' + this.chartWidth() + 'x' + this.chartHeight() + ' points: ' + this.data_points + ' library: ' + this.library_name);
2899 this.redrawChart = function() {
2900 if(this.data !== null)
2901 this.updateChartWithData(this.data);
2904 this.updateChartWithData = function(data) {
2905 if(this.debug === true)
2906 this.log('updateChartWithData() called.');
2908 // this may force the chart to be re-created
2912 this.updates_counter++;
2913 this.updates_since_last_unhide++;
2914 this.updates_since_last_creation++;
2916 var started = Date.now();
2918 // if the result is JSON, find the latest update-every
2919 this.data_update_every = data.view_update_every * 1000;
2920 this.data_after = data.after * 1000;
2921 this.data_before = data.before * 1000;
2922 this.netdata_first = data.first_entry * 1000;
2923 this.netdata_last = data.last_entry * 1000;
2924 this.data_points = data.points;
2927 if(NETDATA.options.current.pan_and_zoom_data_padding === true && this.requested_padding !== null) {
2928 if(this.view_after < this.data_after) {
2929 // console.log('adusting view_after from ' + this.view_after + ' to ' + this.data_after);
2930 this.view_after = this.data_after;
2933 if(this.view_before > this.data_before) {
2934 // console.log('adusting view_before from ' + this.view_before + ' to ' + this.data_before);
2935 this.view_before = this.data_before;
2939 this.view_after = this.data_after;
2940 this.view_before = this.data_before;
2943 if(this.debug === true) {
2944 this.log('UPDATE No ' + this.updates_counter + ' COMPLETED');
2946 if(this.current.force_after_ms)
2947 this.log('STATUS: forced : ' + (this.current.force_after_ms / 1000).toString() + ' - ' + (this.current.force_before_ms / 1000).toString());
2949 this.log('STATUS: forced : unset');
2951 this.log('STATUS: requested : ' + (this.requested_after / 1000).toString() + ' - ' + (this.requested_before / 1000).toString());
2952 this.log('STATUS: downloaded: ' + (this.data_after / 1000).toString() + ' - ' + (this.data_before / 1000).toString());
2953 this.log('STATUS: rendered : ' + (this.view_after / 1000).toString() + ' - ' + (this.view_before / 1000).toString());
2954 this.log('STATUS: points : ' + (this.data_points).toString());
2957 if(this.data_points === 0) {
2962 if(this.updates_since_last_creation >= this.library.max_updates_to_recreate()) {
2963 if(this.debug === true)
2964 this.log('max updates of ' + this.updates_since_last_creation.toString() + ' reached. Forcing re-generation.');
2966 this.chart_created = false;
2969 // check and update the legend
2970 this.legendUpdateDOM();
2972 if(this.chart_created === true
2973 && typeof this.library.update === 'function') {
2975 if(this.debug === true)
2976 this.log('updating chart...');
2978 if(callChartLibraryUpdateSafely(data) === false)
2982 if(this.debug === true)
2983 this.log('creating chart...');
2985 if(callChartLibraryCreateSafely(data) === false)
2989 this.legendShowLatestValues();
2990 if(this.selected === true)
2991 NETDATA.globalSelectionSync.stop();
2993 // update the performance counters
2994 var now = Date.now();
2995 this.tm.last_updated = now;
2997 // don't update last_autorefreshed if this chart is
2998 // forced to be updated with global PanAndZoom
2999 if(NETDATA.globalPanAndZoom.isActive())
3000 this.tm.last_autorefreshed = 0;
3002 if(NETDATA.options.current.parallel_refresher === true && NETDATA.options.current.concurrent_refreshes === true)
3003 this.tm.last_autorefreshed = now - (now % this.data_update_every);
3005 this.tm.last_autorefreshed = now;
3008 this.refresh_dt_ms = now - started;
3009 NETDATA.options.auto_refresher_fast_weight += this.refresh_dt_ms;
3011 if(this.refresh_dt_element !== null)
3012 this.refresh_dt_element.innerHTML = this.refresh_dt_ms.toString();
3015 this.updateChart = function(callback) {
3016 if(this.debug === true)
3017 this.log('updateChart() called.');
3019 if(this._updating === true) {
3020 if(this.debug === true)
3021 this.log('I am already updating...');
3023 if(typeof callback === 'function') callback();
3027 // due to late initialization of charts and libraries
3028 // we need to check this too
3029 if(this.enabled === false) {
3030 if(this.debug === true)
3031 this.log('I am not enabled');
3033 if(typeof callback === 'function') callback();
3037 if(canBeRendered() === false) {
3038 if(typeof callback === 'function') callback();
3042 if(this.chart === null) {
3043 this.getChart(function() { that.updateChart(callback); });
3047 if(this.library.initialized === false) {
3048 if(this.library.enabled === true) {
3049 this.library.initialize(function() { that.updateChart(callback); });
3053 error('chart library "' + this.library_name + '" is not available.');
3054 if(typeof callback === 'function') callback();
3059 this.clearSelection();
3062 if(this.debug === true)
3063 this.log('updating from ' + this.data_url);
3065 NETDATA.statistics.refreshes_total++;
3066 NETDATA.statistics.refreshes_active++;
3068 if(NETDATA.statistics.refreshes_active > NETDATA.statistics.refreshes_active_max)
3069 NETDATA.statistics.refreshes_active_max = NETDATA.statistics.refreshes_active;
3071 this._updating = true;
3073 this.xhr = $.ajax( {
3078 'Cache-Control': 'no-cache, no-store',
3079 'Pragma': 'no-cache'
3081 xhrFields: { withCredentials: true } // required for the cookie
3083 .done(function(data) {
3084 that.xhr = undefined;
3085 that.retries_on_data_failures = 0;
3087 if(that.debug === true)
3088 that.log('data received. updating chart.');
3090 that.updateChartWithData(data);
3092 .fail(function(msg) {
3093 that.xhr = undefined;
3095 if(msg.statusText !== 'abort') {
3096 that.retries_on_data_failures++;
3097 if(that.retries_on_data_failures > NETDATA.options.current.retries_on_data_failures) {
3098 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times - giving up');
3099 that.retries_on_data_failures = 0;
3100 error('data download failed for url: ' + that.data_url);
3103 that.tm.last_autorefreshed = Date.now();
3104 // that.log('failed ' + that.retries_on_data_failures.toString() + ' times, but I will retry');
3108 .always(function() {
3109 that.xhr = undefined;
3111 NETDATA.statistics.refreshes_active--;
3112 that._updating = false;
3113 if(typeof callback === 'function') callback();
3119 this.isVisible = function(nocache) {
3120 if(typeof nocache === 'undefined')
3123 // this.log('last_visible_check: ' + this.tm.last_visible_check + ', last_page_scroll: ' + NETDATA.options.last_page_scroll);
3125 // caching - we do not evaluate the charts visibility
3126 // if the page has not been scrolled since the last check
3127 if(nocache === false && this.tm.last_visible_check > NETDATA.options.last_page_scroll)
3128 return this.___isVisible___;
3130 this.tm.last_visible_check = Date.now();
3132 var wh = window.innerHeight;
3133 var x = this.element.getBoundingClientRect();
3137 if(x.width === 0 || x.height === 0) {
3139 this.___isVisible___ = false;
3140 return this.___isVisible___;
3143 if(x.top < 0 && -x.top > x.height) {
3144 // the chart is entirely above
3145 ret = -x.top - x.height;
3147 else if(x.top > wh) {
3148 // the chart is entirely below
3152 if(ret > tolerance) {
3153 // the chart is too far
3156 this.___isVisible___ = false;
3157 return this.___isVisible___;
3160 // the chart is inside or very close
3163 this.___isVisible___ = true;
3164 return this.___isVisible___;
3168 this.isAutoRefreshable = function() {
3169 return (this.current.autorefresh);
3172 this.canBeAutoRefreshed = function() {
3173 var now = Date.now();
3175 if(this.running === true) {
3176 if(this.debug === true)
3177 this.log('I am already running');
3182 if(this.enabled === false) {
3183 if(this.debug === true)
3184 this.log('I am not enabled');
3189 if(this.library === null || this.library.enabled === false) {
3190 error('charting library "' + this.library_name + '" is not available');
3191 if(this.debug === true)
3192 this.log('My chart library ' + this.library_name + ' is not available');
3197 if(this.isVisible() === false) {
3198 if(NETDATA.options.debug.visibility === true || this.debug === true)
3199 this.log('I am not visible');
3204 if(this.current.force_update_at !== 0 && this.current.force_update_at < now) {
3205 if(this.debug === true)
3206 this.log('timed force update detected - allowing this update');
3208 this.current.force_update_at = 0;
3212 if(this.isAutoRefreshable() === true) {
3213 // allow the first update, even if the page is not visible
3214 if(this.updates_counter && this.updates_since_last_unhide && NETDATA.options.page_is_visible === false) {
3215 if(NETDATA.options.debug.focus === true || this.debug === true)
3216 this.log('canBeAutoRefreshed(): page does not have focus');
3221 if(this.needsRecreation() === true) {
3222 if(this.debug === true)
3223 this.log('canBeAutoRefreshed(): needs re-creation.');
3228 // options valid only for autoRefresh()
3229 if(NETDATA.options.auto_refresher_stop_until === 0 || NETDATA.options.auto_refresher_stop_until < now) {
3230 if(NETDATA.globalPanAndZoom.isActive()) {
3231 if(NETDATA.globalPanAndZoom.shouldBeAutoRefreshed(this)) {
3232 if(this.debug === true)
3233 this.log('canBeAutoRefreshed(): global panning: I need an update.');
3238 if(this.debug === true)
3239 this.log('canBeAutoRefreshed(): global panning: I am already up to date.');
3245 if(this.selected === true) {
3246 if(this.debug === true)
3247 this.log('canBeAutoRefreshed(): I have a selection in place.');
3252 if(this.paused === true) {
3253 if(this.debug === true)
3254 this.log('canBeAutoRefreshed(): I am paused.');
3259 if(now - this.tm.last_autorefreshed >= this.data_update_every) {
3260 if(this.debug === true)
3261 this.log('canBeAutoRefreshed(): It is time to update me.');
3271 this.autoRefresh = function(callback) {
3272 if(this.canBeAutoRefreshed() === true && this.running === false) {
3275 state.running = true;
3276 state.updateChart(function() {
3277 state.running = false;
3279 if(typeof callback !== 'undefined')
3284 if(typeof callback !== 'undefined')
3289 this._defaultsFromDownloadedChart = function(chart) {
3291 this.chart_url = chart.url;
3292 this.data_update_every = chart.update_every * 1000;
3293 this.data_points = Math.round(this.chartWidth() / this.chartPixelsPerPoint());
3294 this.tm.last_info_downloaded = Date.now();
3296 if(this.title === null)
3297 this.title = chart.title;
3299 if(this.units === null)
3300 this.units = chart.units;
3303 // fetch the chart description from the netdata server
3304 this.getChart = function(callback) {
3305 this.chart = NETDATA.chartRegistry.get(this.host, this.id);
3307 this._defaultsFromDownloadedChart(this.chart);
3308 if(typeof callback === 'function') callback();
3311 this.chart_url = "/api/v1/chart?chart=" + this.id;
3313 if(this.debug === true)
3314 this.log('downloading ' + this.chart_url);
3317 url: this.host + this.chart_url,
3320 xhrFields: { withCredentials: true } // required for the cookie
3322 .done(function(chart) {
3323 chart.url = that.chart_url;
3324 that._defaultsFromDownloadedChart(chart);
3325 NETDATA.chartRegistry.add(that.host, that.id, chart);
3328 NETDATA.error(404, that.chart_url);
3329 error('chart not found on url "' + that.chart_url + '"');
3331 .always(function() {
3332 if(typeof callback === 'function') callback();
3337 // ============================================================================================================
3343 NETDATA.resetAllCharts = function(state) {
3344 // first clear the global selection sync
3345 // to make sure no chart is in selected state
3346 state.globalSelectionSyncStop();
3348 // there are 2 possibilities here
3349 // a. state is the global Pan and Zoom master
3350 // b. state is not the global Pan and Zoom master
3352 if(NETDATA.globalPanAndZoom.isMaster(state) === false)
3355 // clear the global Pan and Zoom
3356 // this will also refresh the master
3357 // and unblock any charts currently mirroring the master
3358 NETDATA.globalPanAndZoom.clearMaster();
3360 // if we were not the master, reset our status too
3361 // this is required because most probably the mouse
3362 // is over this chart, blocking it from auto-refreshing
3363 if(master === false && (state.paused === true || state.selected === true))
3367 // get or create a chart state, given a DOM element
3368 NETDATA.chartState = function(element) {
3369 var state = $(element).data('netdata-state-object') || null;
3370 if(state === null) {
3371 state = new chartState(element);
3372 $(element).data('netdata-state-object', state);
3377 // ----------------------------------------------------------------------------------------------------------------
3378 // Library functions
3380 // Load a script without jquery
3381 // This is used to load jquery - after it is loaded, we use jquery
3382 NETDATA._loadjQuery = function(callback) {
3383 if(typeof jQuery === 'undefined') {
3384 if(NETDATA.options.debug.main_loop === true)
3385 console.log('loading ' + NETDATA.jQuery);
3387 var script = document.createElement('script');
3388 script.type = 'text/javascript';
3389 script.async = true;
3390 script.src = NETDATA.jQuery;
3392 // script.onabort = onError;
3393 script.onerror = function(err, t) { NETDATA.error(101, NETDATA.jQuery); };
3394 if(typeof callback === "function")
3395 script.onload = callback;
3397 var s = document.getElementsByTagName('script')[0];
3398 s.parentNode.insertBefore(script, s);
3400 else if(typeof callback === "function")
3404 NETDATA._loadCSS = function(filename) {
3405 // don't use jQuery here
3406 // styles are loaded before jQuery
3407 // to eliminate showing an unstyled page to the user
3409 var fileref = document.createElement("link");
3410 fileref.setAttribute("rel", "stylesheet");
3411 fileref.setAttribute("type", "text/css");
3412 fileref.setAttribute("href", filename);
3414 if (typeof fileref !== 'undefined')
3415 document.getElementsByTagName("head")[0].appendChild(fileref);
3418 NETDATA.colorHex2Rgb = function(hex) {
3419 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
3420 var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
3421 hex = hex.replace(shorthandRegex, function(m, r, g, b) {
3422 return r + r + g + g + b + b;
3425 var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3427 r: parseInt(result[1], 16),
3428 g: parseInt(result[2], 16),
3429 b: parseInt(result[3], 16)
3433 NETDATA.colorLuminance = function(hex, lum) {
3434 // validate hex string
3435 hex = String(hex).replace(/[^0-9a-f]/gi, '');
3437 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
3441 // convert to decimal and change luminosity
3442 var rgb = "#", c, i;
3443 for (i = 0; i < 3; i++) {
3444 c = parseInt(hex.substr(i*2,2), 16);
3445 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
3446 rgb += ("00"+c).substr(c.length);
3452 NETDATA.guid = function() {
3454 return Math.floor((1 + Math.random()) * 0x10000)
3459 return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
3462 NETDATA.zeropad = function(x) {
3463 if(x > -10 && x < 10) return '0' + x.toString();
3464 else return x.toString();
3467 // user function to signal us the DOM has been
3469 NETDATA.updatedDom = function() {
3470 NETDATA.options.updated_dom = true;
3473 NETDATA.ready = function(callback) {
3474 NETDATA.options.pauseCallback = callback;
3477 NETDATA.pause = function(callback) {
3478 if(NETDATA.options.pause === true)
3481 NETDATA.options.pauseCallback = callback;
3484 NETDATA.unpause = function() {
3485 NETDATA.options.pauseCallback = null;
3486 NETDATA.options.updated_dom = true;
3487 NETDATA.options.pause = false;
3490 // ----------------------------------------------------------------------------------------------------------------
3492 // this is purely sequencial charts refresher
3493 // it is meant to be autonomous
3494 NETDATA.chartRefresherNoParallel = function(index) {
3495 if(NETDATA.options.debug.mail_loop === true)
3496 console.log('NETDATA.chartRefresherNoParallel(' + index + ')');
3498 if(NETDATA.options.updated_dom === true) {
3499 // the dom has been updated
3500 // get the dom parts again
3501 NETDATA.parseDom(NETDATA.chartRefresher);
3504 if(index >= NETDATA.options.targets.length) {
3505 if(NETDATA.options.debug.main_loop === true)
3506 console.log('waiting to restart main loop...');
3508 NETDATA.options.auto_refresher_fast_weight = 0;
3510 setTimeout(function() {
3511 NETDATA.chartRefresher();
3512 }, NETDATA.options.current.idle_between_loops);
3515 var state = NETDATA.options.targets[index];
3517 if(NETDATA.options.auto_refresher_fast_weight < NETDATA.options.current.fast_render_timeframe) {
3518 if(NETDATA.options.debug.main_loop === true)
3519 console.log('fast rendering...');
3521 state.autoRefresh(function() {
3522 NETDATA.chartRefresherNoParallel(++index);
3526 if(NETDATA.options.debug.main_loop === true) console.log('waiting for next refresh...');
3527 NETDATA.options.auto_refresher_fast_weight = 0;
3529 setTimeout(function() {
3530 state.autoRefresh(function() {
3531 NETDATA.chartRefresherNoParallel(++index);
3533 }, NETDATA.options.current.idle_between_charts);
3538 // this is part of the parallel refresher
3539 // its cause is to refresh sequencially all the charts
3540 // that depend on chart library initialization
3541 // it will call the parallel refresher back
3542 // as soon as it sees a chart that its chart library
3544 NETDATA.chartRefresher_uninitialized = function() {
3545 if(NETDATA.options.updated_dom === true) {
3546 // the dom has been updated
3547 // get the dom parts again
3548 NETDATA.parseDom(NETDATA.chartRefresher);
3552 if(NETDATA.options.sequencial.length === 0)
3553 NETDATA.chartRefresher();
3555 var state = NETDATA.options.sequencial.pop();
3556 if(state.library.initialized === true)
3557 NETDATA.chartRefresher();
3559 state.autoRefresh(NETDATA.chartRefresher_uninitialized);
3563 NETDATA.chartRefresherWaitTime = function() {
3564 return NETDATA.options.current.idle_parallel_loops;
3567 // the default refresher
3568 // it will create 2 sets of charts:
3569 // - the ones that can be refreshed in parallel
3570 // - the ones that depend on something else
3571 // the first set will be executed in parallel
3572 // the second will be given to NETDATA.chartRefresher_uninitialized()
3573 NETDATA.chartRefresher = function() {
3574 // console.log('auto-refresher...');
3576 if(NETDATA.options.pause === true) {
3577 // console.log('auto-refresher is paused');
3578 setTimeout(NETDATA.chartRefresher,
3579 NETDATA.chartRefresherWaitTime());
3583 if(typeof NETDATA.options.pauseCallback === 'function') {
3584 // console.log('auto-refresher is calling pauseCallback');
3585 NETDATA.options.pause = true;
3586 NETDATA.options.pauseCallback();
3587 NETDATA.chartRefresher();
3591 if(NETDATA.options.current.parallel_refresher === false) {
3592 // console.log('auto-refresher is calling chartRefresherNoParallel(0)');
3593 NETDATA.chartRefresherNoParallel(0);
3597 if(NETDATA.options.updated_dom === true) {
3598 // the dom has been updated
3599 // get the dom parts again
3600 // console.log('auto-refresher is calling parseDom()');
3601 NETDATA.parseDom(NETDATA.chartRefresher);
3605 var parallel = new Array();
3606 var targets = NETDATA.options.targets;
3607 var len = targets.length;
3610 state = targets[len];
3611 if(state.isVisible() === false || state.running === true)
3614 if(state.library.initialized === false) {
3615 if(state.library.enabled === true) {
3616 state.library.initialize(NETDATA.chartRefresher);
3620 state.error('chart library "' + state.library_name + '" is not enabled.');
3624 parallel.unshift(state);
3627 if(parallel.length > 0) {
3628 // console.log('auto-refresher executing in parallel for ' + parallel.length.toString() + ' charts');
3629 // this will execute the jobs in parallel
3630 $(parallel).each(function() {
3635 // console.log('auto-refresher nothing to do');
3638 // run the next refresh iteration
3639 setTimeout(NETDATA.chartRefresher,
3640 NETDATA.chartRefresherWaitTime());
3643 NETDATA.parseDom = function(callback) {
3644 NETDATA.options.last_page_scroll = Date.now();
3645 NETDATA.options.updated_dom = false;
3647 var targets = $('div[data-netdata]'); //.filter(':visible');
3649 if(NETDATA.options.debug.main_loop === true)
3650 console.log('DOM updated - there are ' + targets.length + ' charts on page.');
3652 NETDATA.options.targets = new Array();
3653 var len = targets.length;
3655 // the initialization will take care of sizing
3656 // and the "loading..." message
3657 NETDATA.options.targets.push(NETDATA.chartState(targets[len]));
3660 if(typeof callback === 'function') callback();
3663 // this is the main function - where everything starts
3664 NETDATA.start = function() {
3665 // this should be called only once
3667 NETDATA.options.page_is_visible = true;
3669 $(window).blur(function() {
3670 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3671 NETDATA.options.page_is_visible = false;
3672 if(NETDATA.options.debug.focus === true)
3673 console.log('Lost Focus!');
3677 $(window).focus(function() {
3678 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3679 NETDATA.options.page_is_visible = true;
3680 if(NETDATA.options.debug.focus === true)
3681 console.log('Focus restored!');
3685 if(typeof document.hasFocus === 'function' && !document.hasFocus()) {
3686 if(NETDATA.options.current.stop_updates_when_focus_is_lost === true) {
3687 NETDATA.options.page_is_visible = false;
3688 if(NETDATA.options.debug.focus === true)
3689 console.log('Document has no focus!');
3693 // bootstrap tab switching
3694 $('a[data-toggle="tab"]').on('shown.bs.tab', NETDATA.onscroll);
3696 // bootstrap modal switching
3697 $('.modal').on('hidden.bs.modal', NETDATA.onscroll);
3698 $('.modal').on('shown.bs.modal', NETDATA.onscroll);
3700 // bootstrap collapse switching
3701 $('.collapse').on('hidden.bs.collapse', NETDATA.onscroll);
3702 $('.collapse').on('shown.bs.collapse', NETDATA.onscroll);
3704 NETDATA.parseDom(NETDATA.chartRefresher);
3706 // Alarms initialization
3707 setTimeout(NETDATA.alarms.init, 1000);
3709 // Registry initialization
3710 setTimeout(NETDATA.registry.init, netdataRegistryAfterMs);
3712 if(typeof netdataCallback === 'function')
3716 // ----------------------------------------------------------------------------------------------------------------
3719 NETDATA.peityInitialize = function(callback) {
3720 if(typeof netdataNoPeitys === 'undefined' || !netdataNoPeitys) {
3722 url: NETDATA.peity_js,
3725 xhrFields: { withCredentials: true } // required for the cookie
3728 NETDATA.registerChartLibrary('peity', NETDATA.peity_js);
3731 NETDATA.chartLibraries.peity.enabled = false;
3732 NETDATA.error(100, NETDATA.peity_js);
3734 .always(function() {
3735 if(typeof callback === "function")
3740 NETDATA.chartLibraries.peity.enabled = false;
3741 if(typeof callback === "function")
3746 NETDATA.peityChartUpdate = function(state, data) {
3747 state.peity_instance.innerHTML = data.result;
3749 if(state.peity_options.stroke !== state.chartColors()[0]) {
3750 state.peity_options.stroke = state.chartColors()[0];
3751 if(state.chart.chart_type === 'line')
3752 state.peity_options.fill = NETDATA.themes.current.background;
3754 state.peity_options.fill = NETDATA.colorLuminance(state.chartColors()[0], NETDATA.chartDefaults.fill_luminance);
3757 $(state.peity_instance).peity('line', state.peity_options);
3761 NETDATA.peityChartCreate = function(state, data) {
3762 state.peity_instance = document.createElement('div');
3763 state.element_chart.appendChild(state.peity_instance);
3765 var self = $(state.element);
3766 state.peity_options = {
3767 stroke: NETDATA.themes.current.foreground,
3768 strokeWidth: self.data('peity-strokewidth') || 1,
3769 width: state.chartWidth(),
3770 height: state.chartHeight(),
3771 fill: NETDATA.themes.current.foreground
3774 NETDATA.peityChartUpdate(state, data);
3778 // ----------------------------------------------------------------------------------------------------------------
3781 NETDATA.sparklineInitialize = function(callback) {
3782 if(typeof netdataNoSparklines === 'undefined' || !netdataNoSparklines) {
3784 url: NETDATA.sparkline_js,
3787 xhrFields: { withCredentials: true } // required for the cookie
3790 NETDATA.registerChartLibrary('sparkline', NETDATA.sparkline_js);
3793 NETDATA.chartLibraries.sparkline.enabled = false;
3794 NETDATA.error(100, NETDATA.sparkline_js);
3796 .always(function() {
3797 if(typeof callback === "function")
3802 NETDATA.chartLibraries.sparkline.enabled = false;
3803 if(typeof callback === "function")
3808 NETDATA.sparklineChartUpdate = function(state, data) {
3809 state.sparkline_options.width = state.chartWidth();
3810 state.sparkline_options.height = state.chartHeight();
3812 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3816 NETDATA.sparklineChartCreate = function(state, data) {
3817 var self = $(state.element);
3818 var type = self.data('sparkline-type') || 'line';
3819 var lineColor = self.data('sparkline-linecolor') || state.chartColors()[0];
3820 var fillColor = self.data('sparkline-fillcolor') || (state.chart.chart_type === 'line')?NETDATA.themes.current.background:NETDATA.colorLuminance(lineColor, NETDATA.chartDefaults.fill_luminance);
3821 var chartRangeMin = self.data('sparkline-chartrangemin') || undefined;
3822 var chartRangeMax = self.data('sparkline-chartrangemax') || undefined;
3823 var composite = self.data('sparkline-composite') || undefined;
3824 var enableTagOptions = self.data('sparkline-enabletagoptions') || undefined;
3825 var tagOptionPrefix = self.data('sparkline-tagoptionprefix') || undefined;
3826 var tagValuesAttribute = self.data('sparkline-tagvaluesattribute') || undefined;
3827 var disableHiddenCheck = self.data('sparkline-disablehiddencheck') || undefined;
3828 var defaultPixelsPerValue = self.data('sparkline-defaultpixelspervalue') || undefined;
3829 var spotColor = self.data('sparkline-spotcolor') || undefined;
3830 var minSpotColor = self.data('sparkline-minspotcolor') || undefined;
3831 var maxSpotColor = self.data('sparkline-maxspotcolor') || undefined;
3832 var spotRadius = self.data('sparkline-spotradius') || undefined;
3833 var valueSpots = self.data('sparkline-valuespots') || undefined;
3834 var highlightSpotColor = self.data('sparkline-highlightspotcolor') || undefined;
3835 var highlightLineColor = self.data('sparkline-highlightlinecolor') || undefined;
3836 var lineWidth = self.data('sparkline-linewidth') || undefined;
3837 var normalRangeMin = self.data('sparkline-normalrangemin') || undefined;
3838 var normalRangeMax = self.data('sparkline-normalrangemax') || undefined;
3839 var drawNormalOnTop = self.data('sparkline-drawnormalontop') || undefined;
3840 var xvalues = self.data('sparkline-xvalues') || undefined;
3841 var chartRangeClip = self.data('sparkline-chartrangeclip') || undefined;
3842 var chartRangeMinX = self.data('sparkline-chartrangeminx') || undefined;
3843 var chartRangeMaxX = self.data('sparkline-chartrangemaxx') || undefined;
3844 var disableInteraction = self.data('sparkline-disableinteraction') || false;
3845 var disableTooltips = self.data('sparkline-disabletooltips') || false;
3846 var disableHighlight = self.data('sparkline-disablehighlight') || false;
3847 var highlightLighten = self.data('sparkline-highlightlighten') || 1.4;
3848 var highlightColor = self.data('sparkline-highlightcolor') || undefined;
3849 var tooltipContainer = self.data('sparkline-tooltipcontainer') || undefined;
3850 var tooltipClassname = self.data('sparkline-tooltipclassname') || undefined;
3851 var tooltipFormat = self.data('sparkline-tooltipformat') || undefined;
3852 var tooltipPrefix = self.data('sparkline-tooltipprefix') || undefined;
3853 var tooltipSuffix = self.data('sparkline-tooltipsuffix') || ' ' + state.units;
3854 var tooltipSkipNull = self.data('sparkline-tooltipskipnull') || true;
3855 var tooltipValueLookups = self.data('sparkline-tooltipvaluelookups') || undefined;
3856 var tooltipFormatFieldlist = self.data('sparkline-tooltipformatfieldlist') || undefined;
3857 var tooltipFormatFieldlistKey = self.data('sparkline-tooltipformatfieldlistkey') || undefined;
3858 var numberFormatter = self.data('sparkline-numberformatter') || function(n){ return n.toFixed(2); };
3859 var numberDigitGroupSep = self.data('sparkline-numberdigitgroupsep') || undefined;
3860 var numberDecimalMark = self.data('sparkline-numberdecimalmark') || undefined;
3861 var numberDigitGroupCount = self.data('sparkline-numberdigitgroupcount') || undefined;
3862 var animatedZooms = self.data('sparkline-animatedzooms') || false;
3864 if(spotColor === 'disable') spotColor='';
3865 if(minSpotColor === 'disable') minSpotColor='';
3866 if(maxSpotColor === 'disable') maxSpotColor='';
3868 state.sparkline_options = {
3870 lineColor: lineColor,
3871 fillColor: fillColor,
3872 chartRangeMin: chartRangeMin,
3873 chartRangeMax: chartRangeMax,
3874 composite: composite,
3875 enableTagOptions: enableTagOptions,
3876 tagOptionPrefix: tagOptionPrefix,
3877 tagValuesAttribute: tagValuesAttribute,
3878 disableHiddenCheck: disableHiddenCheck,
3879 defaultPixelsPerValue: defaultPixelsPerValue,
3880 spotColor: spotColor,
3881 minSpotColor: minSpotColor,
3882 maxSpotColor: maxSpotColor,
3883 spotRadius: spotRadius,
3884 valueSpots: valueSpots,
3885 highlightSpotColor: highlightSpotColor,
3886 highlightLineColor: highlightLineColor,
3887 lineWidth: lineWidth,
3888 normalRangeMin: normalRangeMin,
3889 normalRangeMax: normalRangeMax,
3890 drawNormalOnTop: drawNormalOnTop,
3892 chartRangeClip: chartRangeClip,
3893 chartRangeMinX: chartRangeMinX,
3894 chartRangeMaxX: chartRangeMaxX,
3895 disableInteraction: disableInteraction,
3896 disableTooltips: disableTooltips,
3897 disableHighlight: disableHighlight,
3898 highlightLighten: highlightLighten,
3899 highlightColor: highlightColor,
3900 tooltipContainer: tooltipContainer,
3901 tooltipClassname: tooltipClassname,
3902 tooltipChartTitle: state.title,
3903 tooltipFormat: tooltipFormat,
3904 tooltipPrefix: tooltipPrefix,
3905 tooltipSuffix: tooltipSuffix,
3906 tooltipSkipNull: tooltipSkipNull,
3907 tooltipValueLookups: tooltipValueLookups,
3908 tooltipFormatFieldlist: tooltipFormatFieldlist,
3909 tooltipFormatFieldlistKey: tooltipFormatFieldlistKey,
3910 numberFormatter: numberFormatter,
3911 numberDigitGroupSep: numberDigitGroupSep,
3912 numberDecimalMark: numberDecimalMark,
3913 numberDigitGroupCount: numberDigitGroupCount,
3914 animatedZooms: animatedZooms,
3915 width: state.chartWidth(),
3916 height: state.chartHeight()
3919 $(state.element_chart).sparkline(data.result, state.sparkline_options);
3923 // ----------------------------------------------------------------------------------------------------------------
3930 NETDATA.dygraphToolboxPanAndZoom = function(state, after, before) {
3931 if(after < state.netdata_first)
3932 after = state.netdata_first;
3934 if(before > state.netdata_last)
3935 before = state.netdata_last;
3937 state.setMode('zoom');
3938 state.globalSelectionSyncStop();
3939 state.globalSelectionSyncDelay();
3940 state.dygraph_user_action = true;
3941 state.dygraph_force_zoom = true;
3942 state.updateChartPanOrZoom(after, before);
3943 NETDATA.globalPanAndZoom.setMaster(state, after, before);
3946 NETDATA.dygraphSetSelection = function(state, t) {
3947 if(typeof state.dygraph_instance !== 'undefined') {
3948 var r = state.calculateRowForTime(t);
3950 state.dygraph_instance.setSelection(r);
3952 state.dygraph_instance.clearSelection();
3953 state.legendShowUndefined();
3960 NETDATA.dygraphClearSelection = function(state, t) {
3961 if(typeof state.dygraph_instance !== 'undefined') {
3962 state.dygraph_instance.clearSelection();
3967 NETDATA.dygraphSmoothInitialize = function(callback) {
3969 url: NETDATA.dygraph_smooth_js,
3972 xhrFields: { withCredentials: true } // required for the cookie
3975 NETDATA.dygraph.smooth = true;
3976 smoothPlotter.smoothing = 0.3;
3979 NETDATA.dygraph.smooth = false;
3981 .always(function() {
3982 if(typeof callback === "function")
3987 NETDATA.dygraphInitialize = function(callback) {
3988 if(typeof netdataNoDygraphs === 'undefined' || !netdataNoDygraphs) {
3990 url: NETDATA.dygraph_js,
3993 xhrFields: { withCredentials: true } // required for the cookie
3996 NETDATA.registerChartLibrary('dygraph', NETDATA.dygraph_js);
3999 NETDATA.chartLibraries.dygraph.enabled = false;
4000 NETDATA.error(100, NETDATA.dygraph_js);
4002 .always(function() {
4003 if(NETDATA.chartLibraries.dygraph.enabled === true && NETDATA.options.current.smooth_plot === true)
4004 NETDATA.dygraphSmoothInitialize(callback);
4005 else if(typeof callback === "function")
4010 NETDATA.chartLibraries.dygraph.enabled = false;
4011 if(typeof callback === "function")
4016 NETDATA.dygraphChartUpdate = function(state, data) {
4017 var dygraph = state.dygraph_instance;
4019 if(typeof dygraph === 'undefined')
4020 return NETDATA.dygraphChartCreate(state, data);
4022 // when the chart is not visible, and hidden
4023 // if there is a window resize, dygraph detects
4024 // its element size as 0x0.
4025 // this will make it re-appear properly
4027 if(state.tm.last_unhidden > state.dygraph_last_rendered)
4031 file: data.result.data,
4032 colors: state.chartColors(),
4033 labels: data.result.labels,
4034 labelsDivWidth: state.chartWidth() - 70,
4035 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names)
4038 if(state.dygraph_force_zoom === true) {
4039 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4040 state.log('dygraphChartUpdate() forced zoom update');
4042 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4043 options.isZoomedIgnoreProgrammaticZoom = true;
4044 state.dygraph_force_zoom = false;
4046 else if(state.current.name !== 'auto') {
4047 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4048 state.log('dygraphChartUpdate() loose update');
4051 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4052 state.log('dygraphChartUpdate() strict update');
4054 options.dateWindow = (state.requested_padding !== null)?[ state.view_after, state.view_before ]:null;
4055 options.isZoomedIgnoreProgrammaticZoom = true;
4058 options.valueRange = state.dygraph_options.valueRange;
4060 var oldMax = null, oldMin = null;
4061 if(state.__commonMin !== null) {
4062 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4063 oldMin = options.valueRange[0] = NETDATA.commonMin.get(state);
4065 if(state.__commonMax !== null) {
4066 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4067 oldMax = options.valueRange[1] = NETDATA.commonMax.get(state);
4070 if(state.dygraph_smooth_eligible === true) {
4071 if((NETDATA.options.current.smooth_plot === true && state.dygraph_options.plotter !== smoothPlotter)
4072 || (NETDATA.options.current.smooth_plot === false && state.dygraph_options.plotter === smoothPlotter)) {
4073 NETDATA.dygraphChartCreate(state, data);
4078 dygraph.updateOptions(options);
4081 if(oldMin !== null && oldMin > state.dygraph_instance.axes_[0].extremeRange[0]) {
4082 state.data.min = state.dygraph_instance.axes_[0].extremeRange[0];
4083 options.valueRange[0] = NETDATA.commonMin.get(state);
4086 if(oldMax !== null && oldMax < state.dygraph_instance.axes_[0].extremeRange[1]) {
4087 state.data.max = state.dygraph_instance.axes_[0].extremeRange[1];
4088 options.valueRange[1] = NETDATA.commonMax.get(state);
4092 if(redraw === true) {
4093 // state.log('forcing redraw to adapt to common- min/max');
4094 dygraph.updateOptions(options);
4097 state.dygraph_last_rendered = Date.now();
4101 NETDATA.dygraphChartCreate = function(state, data) {
4102 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4103 state.log('dygraphChartCreate()');
4105 var self = $(state.element);
4107 var chart_type = state.chart.chart_type;
4108 if(chart_type === 'stacked' && data.dimensions === 1) chart_type = 'area';
4109 chart_type = self.data('dygraph-type') || chart_type;
4111 var smooth = (chart_type === 'line' && !NETDATA.chartLibraries.dygraph.isSparkline(state))?true:false;
4112 smooth = self.data('dygraph-smooth') || smooth;
4114 if(NETDATA.dygraph.smooth === false)
4117 var strokeWidth = (chart_type === 'stacked')?0.1:((smooth)?1.5:0.7)
4118 var highlightCircleSize = (NETDATA.chartLibraries.dygraph.isSparkline(state))?3:4;
4120 state.dygraph_options = {
4121 colors: self.data('dygraph-colors') || state.chartColors(),
4123 // leave a few pixels empty on the right of the chart
4124 rightGap: self.data('dygraph-rightgap') || 5,
4125 showRangeSelector: self.data('dygraph-showrangeselector') || false,
4126 showRoller: self.data('dygraph-showroller') || false,
4128 title: self.data('dygraph-title') || state.title,
4129 titleHeight: self.data('dygraph-titleheight') || 19,
4131 legend: self.data('dygraph-legend') || 'always', // we need this to get selection events
4132 labels: data.result.labels,
4133 labelsDiv: self.data('dygraph-labelsdiv') || state.element_legend_childs.hidden,
4134 labelsDivStyles: self.data('dygraph-labelsdivstyles') || { 'fontSize':'1px' },
4135 labelsDivWidth: self.data('dygraph-labelsdivwidth') || state.chartWidth() - 70,
4136 labelsSeparateLines: self.data('dygraph-labelsseparatelines') || true,
4137 labelsShowZeroValues: self.data('dygraph-labelsshowzerovalues') || true,
4140 showLabelsOnHighlight: self.data('dygraph-showlabelsonhighlight') || true,
4141 hideOverlayOnMouseOut: self.data('dygraph-hideoverlayonmouseout') || true,
4143 includeZero: self.data('dygraph-includezero') || ((chart_type === 'stacked')? true : false),
4144 xRangePad: self.data('dygraph-xrangepad') || 0,
4145 yRangePad: self.data('dygraph-yrangepad') || 1,
4147 valueRange: self.data('dygraph-valuerange') || [ null, null ],
4149 ylabel: state.units,
4150 yLabelWidth: self.data('dygraph-ylabelwidth') || 12,
4152 // the function to plot the chart
4155 // The width of the lines connecting data points. This can be used to increase the contrast or some graphs.
4156 strokeWidth: self.data('dygraph-strokewidth') || strokeWidth,
4157 strokePattern: self.data('dygraph-strokepattern') || undefined,
4159 // The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated",
4160 // i.e. there is a missing point on either side of it. This also controls the size of those dots.
4161 drawPoints: self.data('dygraph-drawpoints') || false,
4163 // Draw points at the edges of gaps in the data. This improves visibility of small data segments or other data irregularities.
4164 drawGapEdgePoints: self.data('dygraph-drawgapedgepoints') || true,
4166 connectSeparatedPoints: self.data('dygraph-connectseparatedpoints') || false,
4167 pointSize: self.data('dygraph-pointsize') || 1,
4169 // enabling this makes the chart with little square lines
4170 stepPlot: self.data('dygraph-stepplot') || false,
4172 // Draw a border around graph lines to make crossing lines more easily distinguishable. Useful for graphs with many lines.
4173 strokeBorderColor: self.data('dygraph-strokebordercolor') || NETDATA.themes.current.background,
4174 strokeBorderWidth: self.data('dygraph-strokeborderwidth') || (chart_type === 'stacked')?0.0:0.0,
4176 fillGraph: self.data('dygraph-fillgraph') || (chart_type === 'area' || chart_type === 'stacked')?true:false,
4177 fillAlpha: self.data('dygraph-fillalpha') || (chart_type === 'stacked')?NETDATA.options.current.color_fill_opacity_stacked:NETDATA.options.current.color_fill_opacity_area,
4178 stackedGraph: self.data('dygraph-stackedgraph') || (chart_type === 'stacked')?true:false,
4179 stackedGraphNaNFill: self.data('dygraph-stackedgraphnanfill') || 'none',
4181 drawAxis: self.data('dygraph-drawaxis') || true,
4182 axisLabelFontSize: self.data('dygraph-axislabelfontsize') || 10,
4183 axisLineColor: self.data('dygraph-axislinecolor') || NETDATA.themes.current.axis,
4184 axisLineWidth: self.data('dygraph-axislinewidth') || 1.0,
4186 drawGrid: self.data('dygraph-drawgrid') || true,
4187 gridLinePattern: self.data('dygraph-gridlinepattern') || null,
4188 gridLineWidth: self.data('dygraph-gridlinewidth') || 1.0,
4189 gridLineColor: self.data('dygraph-gridlinecolor') || NETDATA.themes.current.grid,
4191 maxNumberWidth: self.data('dygraph-maxnumberwidth') || 8,
4192 sigFigs: self.data('dygraph-sigfigs') || null,
4193 digitsAfterDecimal: self.data('dygraph-digitsafterdecimal') || 2,
4194 valueFormatter: self.data('dygraph-valueformatter') || function(x){ return x.toFixed(2); },
4196 highlightCircleSize: self.data('dygraph-highlightcirclesize') || highlightCircleSize,
4197 highlightSeriesOpts: self.data('dygraph-highlightseriesopts') || null, // TOO SLOW: { strokeWidth: 1.5 },
4198 highlightSeriesBackgroundAlpha: self.data('dygraph-highlightseriesbackgroundalpha') || null, // TOO SLOW: (chart_type === 'stacked')?0.7:0.5,
4200 pointClickCallback: self.data('dygraph-pointclickcallback') || undefined,
4201 visibility: state.dimensions_visibility.selected2BooleanArray(state.data.dimension_names),
4205 ticker: Dygraph.dateTicker,
4206 axisLabelFormatter: function (d, gran) {
4207 return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4209 valueFormatter: function (ms) {
4210 var d = new Date(ms);
4211 return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();
4212 // return NETDATA.zeropad(d.getHours()) + ":" + NETDATA.zeropad(d.getMinutes()) + ":" + NETDATA.zeropad(d.getSeconds());
4217 valueFormatter: function (x) {
4218 // we format legends with the state object
4219 // no need to do anything here
4220 // return (Math.round(x*100) / 100).toLocaleString();
4221 // return state.legendFormatValue(x);
4226 legendFormatter: function(data) {
4227 var elements = state.element_legend_childs;
4229 // if the hidden div is not there
4230 // we are not managing the legend
4231 if(elements.hidden === null) return;
4233 if (typeof data.x !== 'undefined') {
4234 state.legendSetDate(data.x);
4235 var i = data.series.length;
4237 var series = data.series[i];
4238 if(!series.isVisible) continue;
4239 state.legendSetLabelValue(series.label, series.y);
4245 drawCallback: function(dygraph, is_initial) {
4246 if(state.current.name !== 'auto' && state.dygraph_user_action === true) {
4247 state.dygraph_user_action = false;
4249 var x_range = dygraph.xAxisRange();
4250 var after = Math.round(x_range[0]);
4251 var before = Math.round(x_range[1]);
4253 if(NETDATA.options.debug.dygraph === true)
4254 state.log('dygraphDrawCallback(dygraph, ' + is_initial + '): ' + (after / 1000).toString() + ' - ' + (before / 1000).toString());
4256 if(before <= state.netdata_last && after >= state.netdata_first)
4257 state.updateChartPanOrZoom(after, before);
4260 zoomCallback: function(minDate, maxDate, yRanges) {
4261 if(NETDATA.options.debug.dygraph === true)
4262 state.log('dygraphZoomCallback()');
4264 state.globalSelectionSyncStop();
4265 state.globalSelectionSyncDelay();
4266 state.setMode('zoom');
4268 // refresh it to the greatest possible zoom level
4269 state.dygraph_user_action = true;
4270 state.dygraph_force_zoom = true;
4271 state.updateChartPanOrZoom(minDate, maxDate);
4273 highlightCallback: function(event, x, points, row, seriesName) {
4274 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4275 state.log('dygraphHighlightCallback()');
4279 // there is a bug in dygraph when the chart is zoomed enough
4280 // the time it thinks is selected is wrong
4281 // here we calculate the time t based on the row number selected
4283 var t = state.data_after + row * state.data_update_every;
4284 // 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);
4286 state.globalSelectionSync(x);
4288 // fix legend zIndex using the internal structures of dygraph legend module
4289 // this works, but it is a hack!
4290 // state.dygraph_instance.plugins_[0].plugin.legend_div_.style.zIndex = 10000;
4292 unhighlightCallback: function(event) {
4293 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4294 state.log('dygraphUnhighlightCallback()');
4296 state.unpauseChart();
4297 state.globalSelectionSyncStop();
4299 interactionModel : {
4300 mousedown: function(event, dygraph, context) {
4301 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4302 state.log('interactionModel.mousedown()');
4304 state.dygraph_user_action = true;
4305 state.globalSelectionSyncStop();
4307 if(NETDATA.options.debug.dygraph === true)
4308 state.log('dygraphMouseDown()');
4310 // Right-click should not initiate a zoom.
4311 if(event.button && event.button === 2) return;
4313 context.initializeMouseDown(event, dygraph, context);
4315 if(event.button && event.button === 1) {
4316 if (event.altKey || event.shiftKey) {
4317 state.setMode('pan');
4318 state.globalSelectionSyncDelay();
4319 Dygraph.startPan(event, dygraph, context);
4322 state.setMode('zoom');
4323 state.globalSelectionSyncDelay();
4324 Dygraph.startZoom(event, dygraph, context);
4328 if (event.altKey || event.shiftKey) {
4329 state.setMode('zoom');
4330 state.globalSelectionSyncDelay();
4331 Dygraph.startZoom(event, dygraph, context);
4334 state.setMode('pan');
4335 state.globalSelectionSyncDelay();
4336 Dygraph.startPan(event, dygraph, context);
4340 mousemove: function(event, dygraph, context) {
4341 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4342 state.log('interactionModel.mousemove()');
4344 if(context.isPanning) {
4345 state.dygraph_user_action = true;
4346 state.globalSelectionSyncStop();
4347 state.globalSelectionSyncDelay();
4348 state.setMode('pan');
4349 context.is2DPan = false;
4350 Dygraph.movePan(event, dygraph, context);
4352 else if(context.isZooming) {
4353 state.dygraph_user_action = true;
4354 state.globalSelectionSyncStop();
4355 state.globalSelectionSyncDelay();
4356 state.setMode('zoom');
4357 Dygraph.moveZoom(event, dygraph, context);
4360 mouseup: function(event, dygraph, context) {
4361 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4362 state.log('interactionModel.mouseup()');
4364 if (context.isPanning) {
4365 state.dygraph_user_action = true;
4366 state.globalSelectionSyncDelay();
4367 Dygraph.endPan(event, dygraph, context);
4369 else if (context.isZooming) {
4370 state.dygraph_user_action = true;
4371 state.globalSelectionSyncDelay();
4372 Dygraph.endZoom(event, dygraph, context);
4375 click: function(event, dygraph, context) {
4376 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4377 state.log('interactionModel.click()');
4379 event.preventDefault();
4381 dblclick: function(event, dygraph, context) {
4382 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4383 state.log('interactionModel.dblclick()');
4384 NETDATA.resetAllCharts(state);
4386 wheel: function(event, dygraph, context) {
4387 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4388 state.log('interactionModel.wheel()');
4390 // Take the offset of a mouse event on the dygraph canvas and
4391 // convert it to a pair of percentages from the bottom left.
4392 // (Not top left, bottom is where the lower value is.)
4393 function offsetToPercentage(g, offsetX, offsetY) {
4394 // This is calculating the pixel offset of the leftmost date.
4395 var xOffset = g.toDomCoords(g.xAxisRange()[0], null)[0];
4396 var yar0 = g.yAxisRange(0);
4398 // This is calculating the pixel of the higest value. (Top pixel)
4399 var yOffset = g.toDomCoords(null, yar0[1])[1];
4401 // x y w and h are relative to the corner of the drawing area,
4402 // so that the upper corner of the drawing area is (0, 0).
4403 var x = offsetX - xOffset;
4404 var y = offsetY - yOffset;
4406 // This is computing the rightmost pixel, effectively defining the
4408 var w = g.toDomCoords(g.xAxisRange()[1], null)[0] - xOffset;
4410 // This is computing the lowest pixel, effectively defining the height.
4411 var h = g.toDomCoords(null, yar0[0])[1] - yOffset;
4413 // Percentage from the left.
4414 var xPct = w === 0 ? 0 : (x / w);
4415 // Percentage from the top.
4416 var yPct = h === 0 ? 0 : (y / h);
4418 // The (1-) part below changes it from "% distance down from the top"
4419 // to "% distance up from the bottom".
4420 return [xPct, (1-yPct)];
4423 // Adjusts [x, y] toward each other by zoomInPercentage%
4424 // Split it so the left/bottom axis gets xBias/yBias of that change and
4425 // tight/top gets (1-xBias)/(1-yBias) of that change.
4427 // If a bias is missing it splits it down the middle.
4428 function zoomRange(g, zoomInPercentage, xBias, yBias) {
4429 xBias = xBias || 0.5;
4430 yBias = yBias || 0.5;
4432 function adjustAxis(axis, zoomInPercentage, bias) {
4433 var delta = axis[1] - axis[0];
4434 var increment = delta * zoomInPercentage;
4435 var foo = [increment * bias, increment * (1-bias)];
4437 return [ axis[0] + foo[0], axis[1] - foo[1] ];
4440 var yAxes = g.yAxisRanges();
4442 for (var i = 0; i < yAxes.length; i++) {
4443 newYAxes[i] = adjustAxis(yAxes[i], zoomInPercentage, yBias);
4446 return adjustAxis(g.xAxisRange(), zoomInPercentage, xBias);
4449 if(event.altKey || event.shiftKey) {
4450 state.dygraph_user_action = true;
4452 state.globalSelectionSyncStop();
4453 state.globalSelectionSyncDelay();
4455 // http://dygraphs.com/gallery/interaction-api.js
4457 if(typeof event.wheelDelta === 'number' && event.wheelDelta != NaN)
4459 normal_def = event.wheelDelta / 40;
4462 normal_def = event.deltaY * -1.2;
4464 var normal = (event.detail) ? event.detail * -1 : normal_def;
4465 var percentage = normal / 50;
4467 if (!(event.offsetX && event.offsetY)){
4468 event.offsetX = event.layerX - event.target.offsetLeft;
4469 event.offsetY = event.layerY - event.target.offsetTop;
4472 var percentages = offsetToPercentage(dygraph, event.offsetX, event.offsetY);
4473 var xPct = percentages[0];
4474 var yPct = percentages[1];
4476 var new_x_range = zoomRange(dygraph, percentage, xPct, yPct);
4477 var after = new_x_range[0];
4478 var before = new_x_range[1];
4480 var first = state.netdata_first + state.data_update_every;
4481 var last = state.netdata_last + state.data_update_every;
4484 after -= (before - last);
4491 state.setMode('zoom');
4492 if(state.updateChartPanOrZoom(after, before) === true)
4493 dygraph.updateOptions({ dateWindow: [ after, before ] });
4495 event.preventDefault();
4498 touchstart: function(event, dygraph, context) {
4499 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4500 state.log('interactionModel.touchstart()');
4502 state.dygraph_user_action = true;
4503 state.setMode('zoom');
4506 Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
4508 // we overwrite the touch directions at the end, to overwrite
4509 // the internal default of dygraphs
4510 context.touchDirections = { x: true, y: false };
4512 state.dygraph_last_touch_start = Date.now();
4513 state.dygraph_last_touch_move = 0;
4515 if(typeof event.touches[0].pageX === 'number')
4516 state.dygraph_last_touch_page_x = event.touches[0].pageX;
4518 state.dygraph_last_touch_page_x = 0;
4520 touchmove: function(event, dygraph, context) {
4521 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4522 state.log('interactionModel.touchmove()');
4524 state.dygraph_user_action = true;
4525 Dygraph.defaultInteractionModel.touchmove(event, dygraph, context);
4527 state.dygraph_last_touch_move = Date.now();
4529 touchend: function(event, dygraph, context) {
4530 if(NETDATA.options.debug.dygraph === true || state.debug === true)
4531 state.log('interactionModel.touchend()');
4533 state.dygraph_user_action = true;
4534 Dygraph.defaultInteractionModel.touchend(event, dygraph, context);
4536 // if it didn't move, it is a selection
4537 if(state.dygraph_last_touch_move === 0 && state.dygraph_last_touch_page_x !== 0) {
4538 // internal api of dygraphs
4539 var pct = (state.dygraph_last_touch_page_x - (dygraph.plotter_.area.x + state.element.getBoundingClientRect().left)) / dygraph.plotter_.area.w;
4540 var t = Math.round(state.data_after + (state.data_before - state.data_after) * pct);
4541 if(NETDATA.dygraphSetSelection(state, t) === true)
4542 state.globalSelectionSync(t);
4545 // if it was double tap within double click time, reset the charts
4546 var now = Date.now();
4547 if(typeof state.dygraph_last_touch_end !== 'undefined') {
4548 if(state.dygraph_last_touch_move === 0) {
4549 var dt = now - state.dygraph_last_touch_end;
4550 if(dt <= NETDATA.options.current.double_click_speed)
4551 NETDATA.resetAllCharts(state);
4555 // remember the timestamp of the last touch end
4556 state.dygraph_last_touch_end = now;
4561 if(NETDATA.chartLibraries.dygraph.isSparkline(state)) {
4562 state.dygraph_options.drawGrid = false;
4563 state.dygraph_options.drawAxis = false;
4564 state.dygraph_options.title = undefined;
4565 state.dygraph_options.ylabel = undefined;
4566 state.dygraph_options.yLabelWidth = 0;
4567 state.dygraph_options.labelsDivWidth = 120;
4568 state.dygraph_options.labelsDivStyles.width = '120px';
4569 state.dygraph_options.labelsSeparateLines = true;
4570 state.dygraph_options.rightGap = 0;
4571 state.dygraph_options.yRangePad = 1;
4574 if(smooth === true) {
4575 state.dygraph_smooth_eligible = true;
4577 if(NETDATA.options.current.smooth_plot === true)
4578 state.dygraph_options.plotter = smoothPlotter;
4580 else state.dygraph_smooth_eligible = false;
4582 state.dygraph_instance = new Dygraph(state.element_chart,
4583 data.result.data, state.dygraph_options);
4585 state.dygraph_force_zoom = false;
4586 state.dygraph_user_action = false;
4587 state.dygraph_last_rendered = Date.now();
4589 if(typeof state.dygraph_instance.axes_[0].extremeRange !== 'undefined') {
4590 state.__commonMin = self.data('common-min') || null;
4591 state.__commonMax = self.data('common-max') || null;
4594 state.log('incompatible version of dygraphs detected');
4595 state.__commonMin = null;
4596 state.__commonMax = null;
4602 // ----------------------------------------------------------------------------------------------------------------
4605 NETDATA.morrisInitialize = function(callback) {
4606 if(typeof netdataNoMorris === 'undefined' || !netdataNoMorris) {
4608 // morris requires raphael
4609 if(!NETDATA.chartLibraries.raphael.initialized) {
4610 if(NETDATA.chartLibraries.raphael.enabled) {
4611 NETDATA.raphaelInitialize(function() {
4612 NETDATA.morrisInitialize(callback);
4616 NETDATA.chartLibraries.morris.enabled = false;
4617 if(typeof callback === "function")
4622 NETDATA._loadCSS(NETDATA.morris_css);
4625 url: NETDATA.morris_js,
4628 xhrFields: { withCredentials: true } // required for the cookie
4631 NETDATA.registerChartLibrary('morris', NETDATA.morris_js);
4634 NETDATA.chartLibraries.morris.enabled = false;
4635 NETDATA.error(100, NETDATA.morris_js);
4637 .always(function() {
4638 if(typeof callback === "function")
4644 NETDATA.chartLibraries.morris.enabled = false;
4645 if(typeof callback === "function")
4650 NETDATA.morrisChartUpdate = function(state, data) {
4651 state.morris_instance.setData(data.result.data);
4655 NETDATA.morrisChartCreate = function(state, data) {
4657 state.morris_options = {
4658 element: state.element_chart.id,
4659 data: data.result.data,
4661 ykeys: data.dimension_names,
4662 labels: data.dimension_names,
4668 continuousLine: false,
4669 behaveLikeLine: false
4672 if(state.chart.chart_type === 'line')
4673 state.morris_instance = new Morris.Line(state.morris_options);
4675 else if(state.chart.chart_type === 'area') {
4676 state.morris_options.behaveLikeLine = true;
4677 state.morris_instance = new Morris.Area(state.morris_options);
4680 state.morris_instance = new Morris.Area(state.morris_options);
4685 // ----------------------------------------------------------------------------------------------------------------
4688 NETDATA.raphaelInitialize = function(callback) {
4689 if(typeof netdataStopRaphael === 'undefined' || !netdataStopRaphael) {
4691 url: NETDATA.raphael_js,
4694 xhrFields: { withCredentials: true } // required for the cookie
4697 NETDATA.registerChartLibrary('raphael', NETDATA.raphael_js);
4700 NETDATA.chartLibraries.raphael.enabled = false;
4701 NETDATA.error(100, NETDATA.raphael_js);
4703 .always(function() {
4704 if(typeof callback === "function")
4709 NETDATA.chartLibraries.raphael.enabled = false;
4710 if(typeof callback === "function")
4715 NETDATA.raphaelChartUpdate = function(state, data) {
4716 $(state.element_chart).raphael(data.result, {
4717 width: state.chartWidth(),
4718 height: state.chartHeight()
4724 NETDATA.raphaelChartCreate = function(state, data) {
4725 $(state.element_chart).raphael(data.result, {
4726 width: state.chartWidth(),
4727 height: state.chartHeight()
4733 // ----------------------------------------------------------------------------------------------------------------
4736 NETDATA.c3Initialize = function(callback) {
4737 if(typeof netdataNoC3 === 'undefined' || !netdataNoC3) {
4740 if(!NETDATA.chartLibraries.d3.initialized) {
4741 if(NETDATA.chartLibraries.d3.enabled) {
4742 NETDATA.d3Initialize(function() {
4743 NETDATA.c3Initialize(callback);
4747 NETDATA.chartLibraries.c3.enabled = false;
4748 if(typeof callback === "function")
4753 NETDATA._loadCSS(NETDATA.c3_css);
4759 xhrFields: { withCredentials: true } // required for the cookie
4762 NETDATA.registerChartLibrary('c3', NETDATA.c3_js);
4765 NETDATA.chartLibraries.c3.enabled = false;
4766 NETDATA.error(100, NETDATA.c3_js);
4768 .always(function() {
4769 if(typeof callback === "function")
4775 NETDATA.chartLibraries.c3.enabled = false;
4776 if(typeof callback === "function")
4781 NETDATA.c3ChartUpdate = function(state, data) {
4782 state.c3_instance.destroy();
4783 return NETDATA.c3ChartCreate(state, data);
4785 //state.c3_instance.load({
4786 // rows: data.result,
4793 NETDATA.c3ChartCreate = function(state, data) {
4795 state.element_chart.id = 'c3-' + state.uuid;
4796 // console.log('id = ' + state.element_chart.id);
4798 state.c3_instance = c3.generate({
4799 bindto: '#' + state.element_chart.id,
4801 width: state.chartWidth(),
4802 height: state.chartHeight()
4805 pattern: state.chartColors()
4810 type: (state.chart.chart_type === 'line')?'spline':'area-spline'
4816 format: function(x) {
4817 return NETDATA.zeropad(x.getHours()) + ":" + NETDATA.zeropad(x.getMinutes()) + ":" + NETDATA.zeropad(x.getSeconds());
4844 // console.log(state.c3_instance);
4849 // ----------------------------------------------------------------------------------------------------------------
4852 NETDATA.d3Initialize = function(callback) {
4853 if(typeof netdataStopD3 === 'undefined' || !netdataStopD3) {
4858 xhrFields: { withCredentials: true } // required for the cookie
4861 NETDATA.registerChartLibrary('d3', NETDATA.d3_js);
4864 NETDATA.chartLibraries.d3.enabled = false;
4865 NETDATA.error(100, NETDATA.d3_js);
4867 .always(function() {
4868 if(typeof callback === "function")
4873 NETDATA.chartLibraries.d3.enabled = false;
4874 if(typeof callback === "function")
4879 NETDATA.d3ChartUpdate = function(state, data) {
4883 NETDATA.d3ChartCreate = function(state, data) {
4887 // ----------------------------------------------------------------------------------------------------------------
4890 NETDATA.googleInitialize = function(callback) {
4891 if(typeof netdataNoGoogleCharts === 'undefined' || !netdataNoGoogleCharts) {
4893 url: NETDATA.google_js,
4896 xhrFields: { withCredentials: true } // required for the cookie
4899 NETDATA.registerChartLibrary('google', NETDATA.google_js);
4900 google.load('visualization', '1.1', {
4901 'packages': ['corechart', 'controls'],
4902 'callback': callback
4906 NETDATA.chartLibraries.google.enabled = false;
4907 NETDATA.error(100, NETDATA.google_js);
4908 if(typeof callback === "function")
4913 NETDATA.chartLibraries.google.enabled = false;
4914 if(typeof callback === "function")
4919 NETDATA.googleChartUpdate = function(state, data) {
4920 var datatable = new google.visualization.DataTable(data.result);
4921 state.google_instance.draw(datatable, state.google_options);
4925 NETDATA.googleChartCreate = function(state, data) {
4926 var datatable = new google.visualization.DataTable(data.result);
4928 state.google_options = {
4929 colors: state.chartColors(),
4931 // do not set width, height - the chart resizes itself
4932 //width: state.chartWidth(),
4933 //height: state.chartHeight(),
4938 // title: "Time of Day",
4939 // format:'HH:mm:ss',
4940 viewWindowMode: 'maximized',
4952 viewWindowMode: 'pretty',
4967 focusTarget: 'category',
4974 titlePosition: 'out',
4985 curveType: 'function',
4990 switch(state.chart.chart_type) {
4992 state.google_options.vAxis.viewWindowMode = 'maximized';
4993 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_area;
4994 state.google_instance = new google.visualization.AreaChart(state.element_chart);
4998 state.google_options.isStacked = true;
4999 state.google_options.areaOpacity = NETDATA.options.current.color_fill_opacity_stacked;
5000 state.google_options.vAxis.viewWindowMode = 'maximized';
5001 state.google_options.vAxis.minValue = null;
5002 state.google_options.vAxis.maxValue = null;
5003 state.google_instance = new google.visualization.AreaChart(state.element_chart);
5008 state.google_options.lineWidth = 2;
5009 state.google_instance = new google.visualization.LineChart(state.element_chart);
5013 state.google_instance.draw(datatable, state.google_options);
5017 // ----------------------------------------------------------------------------------------------------------------
5019 NETDATA.easypiechartPercentFromValueMinMax = function(value, min, max) {
5020 if(typeof value !== 'number') value = 0;
5021 if(typeof min !== 'number') min = 0;
5022 if(typeof max !== 'number') max = 0;
5024 if(min > value) min = value;
5025 if(max < value) max = value;
5027 // make sure it is zero based
5028 if(min > 0) min = 0;
5029 if(max < 0) max = 0;
5034 pcent = Math.round(value * 100 / max);
5035 if(pcent === 0) pcent = 0.1;
5039 pcent = Math.round(-value * 100 / min);
5040 if(pcent === 0) pcent = -0.1;
5046 // ----------------------------------------------------------------------------------------------------------------
5049 NETDATA.easypiechartInitialize = function(callback) {
5050 if(typeof netdataNoEasyPieChart === 'undefined' || !netdataNoEasyPieChart) {
5052 url: NETDATA.easypiechart_js,
5055 xhrFields: { withCredentials: true } // required for the cookie
5058 NETDATA.registerChartLibrary('easypiechart', NETDATA.easypiechart_js);
5061 NETDATA.chartLibraries.easypiechart.enabled = false;
5062 NETDATA.error(100, NETDATA.easypiechart_js);
5064 .always(function() {
5065 if(typeof callback === "function")
5070 NETDATA.chartLibraries.easypiechart.enabled = false;
5071 if(typeof callback === "function")
5076 NETDATA.easypiechartClearSelection = function(state) {
5077 if(typeof state.easyPieChartEvent !== 'undefined') {
5078 if(state.easyPieChartEvent.timer !== null)
5079 clearTimeout(state.easyPieChartEvent.timer);
5081 state.easyPieChartEvent.timer = null;
5084 if(state.isAutoRefreshable() === true && state.data !== null) {
5085 NETDATA.easypiechartChartUpdate(state, state.data);
5088 state.easyPieChartLabel.innerHTML = state.legendFormatValue(null);
5089 state.easyPieChart_instance.update(0);
5091 state.easyPieChart_instance.enableAnimation();
5096 NETDATA.easypiechartSetSelection = function(state, t) {
5097 if(state.timeIsVisible(t) !== true)
5098 return NETDATA.easypiechartClearSelection(state);
5100 var slot = state.calculateRowForTime(t);
5101 if(slot < 0 || slot >= state.data.result.length)
5102 return NETDATA.easypiechartClearSelection(state);
5104 if(typeof state.easyPieChartEvent === 'undefined') {
5105 state.easyPieChartEvent = {
5112 var value = state.data.result[state.data.result.length - 1 - slot];
5113 var min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5114 var max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5115 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5117 state.easyPieChartEvent.value = value;
5118 state.easyPieChartEvent.pcent = pcent;
5119 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
5121 if(state.easyPieChartEvent.timer === null) {
5122 state.easyPieChart_instance.disableAnimation();
5124 state.easyPieChartEvent.timer = setTimeout(function() {
5125 state.easyPieChartEvent.timer = null;
5126 state.easyPieChart_instance.update(state.easyPieChartEvent.pcent);
5127 }, NETDATA.options.current.charts_selection_animation_delay);
5133 NETDATA.easypiechartChartUpdate = function(state, data) {
5134 var value, min, max, pcent;
5136 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5141 value = data.result[0];
5142 min = (state.easyPieChartMin === null)?NETDATA.commonMin.get(state):state.easyPieChartMin;
5143 max = (state.easyPieChartMax === null)?NETDATA.commonMax.get(state):state.easyPieChartMax;
5144 pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5147 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
5148 state.easyPieChart_instance.update(pcent);
5152 NETDATA.easypiechartChartCreate = function(state, data) {
5153 var self = $(state.element);
5154 var chart = $(state.element_chart);
5156 var value = data.result[0];
5157 var min = self.data('easypiechart-min-value') || null;
5158 var max = self.data('easypiechart-max-value') || null;
5159 var adjust = self.data('easypiechart-adjust') || null;
5162 min = NETDATA.commonMin.get(state);
5163 state.easyPieChartMin = null;
5166 state.easyPieChartMin = min;
5169 max = NETDATA.commonMax.get(state);
5170 state.easyPieChartMax = null;
5173 state.easyPieChartMax = max;
5175 var pcent = NETDATA.easypiechartPercentFromValueMinMax(value, min, max);
5177 chart.data('data-percent', pcent);
5181 case 'width': size = state.chartHeight(); break;
5182 case 'min': size = Math.min(state.chartWidth(), state.chartHeight()); break;
5183 case 'max': size = Math.max(state.chartWidth(), state.chartHeight()); break;
5185 default: size = state.chartWidth(); break;
5187 state.element.style.width = size + 'px';
5188 state.element.style.height = size + 'px';
5190 var stroke = Math.floor(size / 22);
5191 if(stroke < 3) stroke = 2;
5193 var valuefontsize = Math.floor((size * 2 / 3) / 5);
5194 var valuetop = Math.round((size - valuefontsize - (size / 40)) / 2);
5195 state.easyPieChartLabel = document.createElement('span');
5196 state.easyPieChartLabel.className = 'easyPieChartLabel';
5197 state.easyPieChartLabel.innerHTML = state.legendFormatValue(value);
5198 state.easyPieChartLabel.style.fontSize = valuefontsize + 'px';
5199 state.easyPieChartLabel.style.top = valuetop.toString() + 'px';
5200 state.element_chart.appendChild(state.easyPieChartLabel);
5202 var titlefontsize = Math.round(valuefontsize * 1.6 / 3);
5203 var titletop = Math.round(valuetop - (titlefontsize * 2) - (size / 40));
5204 state.easyPieChartTitle = document.createElement('span');
5205 state.easyPieChartTitle.className = 'easyPieChartTitle';
5206 state.easyPieChartTitle.innerHTML = state.title;
5207 state.easyPieChartTitle.style.fontSize = titlefontsize + 'px';
5208 state.easyPieChartTitle.style.lineHeight = titlefontsize + 'px';
5209 state.easyPieChartTitle.style.top = titletop.toString() + 'px';
5210 state.element_chart.appendChild(state.easyPieChartTitle);
5212 var unitfontsize = Math.round(titlefontsize * 0.9);
5213 var unittop = Math.round(valuetop + (valuefontsize + unitfontsize) + (size / 40));
5214 state.easyPieChartUnits = document.createElement('span');
5215 state.easyPieChartUnits.className = 'easyPieChartUnits';
5216 state.easyPieChartUnits.innerHTML = state.units;
5217 state.easyPieChartUnits.style.fontSize = unitfontsize + 'px';
5218 state.easyPieChartUnits.style.top = unittop.toString() + 'px';
5219 state.element_chart.appendChild(state.easyPieChartUnits);
5221 var barColor = self.data('easypiechart-barcolor');
5222 if(typeof barColor === 'undefined' || barColor === null)
5223 barColor = state.chartColors()[0];
5225 // <div ... data-easypiechart-barcolor="(function(percent){return(percent < 50 ? '#5cb85c' : percent < 85 ? '#f0ad4e' : '#cb3935');})" ...></div>
5226 var tmp = eval(barColor);
5227 if(typeof tmp === 'function')
5231 chart.easyPieChart({
5233 trackColor: self.data('easypiechart-trackcolor') || NETDATA.themes.current.easypiechart_track,
5234 scaleColor: self.data('easypiechart-scalecolor') || NETDATA.themes.current.easypiechart_scale,
5235 scaleLength: self.data('easypiechart-scalelength') || 5,
5236 lineCap: self.data('easypiechart-linecap') || 'round',
5237 lineWidth: self.data('easypiechart-linewidth') || stroke,
5238 trackWidth: self.data('easypiechart-trackwidth') || undefined,
5239 size: self.data('easypiechart-size') || size,
5240 rotate: self.data('easypiechart-rotate') || 0,
5241 animate: self.data('easypiechart-animate') || {duration: 500, enabled: true},
5242 easing: self.data('easypiechart-easing') || undefined
5245 // when we just re-create the chart
5246 // do not animate the first update
5248 if(typeof state.easyPieChart_instance !== 'undefined')
5251 state.easyPieChart_instance = chart.data('easyPieChart');
5252 if(animate === false) state.easyPieChart_instance.disableAnimation();
5253 state.easyPieChart_instance.update(pcent);
5254 if(animate === false) state.easyPieChart_instance.enableAnimation();
5258 // ----------------------------------------------------------------------------------------------------------------
5261 NETDATA.gaugeInitialize = function(callback) {
5262 if(typeof netdataNoGauge === 'undefined' || !netdataNoGauge) {
5264 url: NETDATA.gauge_js,
5267 xhrFields: { withCredentials: true } // required for the cookie
5270 NETDATA.registerChartLibrary('gauge', NETDATA.gauge_js);
5273 NETDATA.chartLibraries.gauge.enabled = false;
5274 NETDATA.error(100, NETDATA.gauge_js);
5276 .always(function() {
5277 if(typeof callback === "function")
5282 NETDATA.chartLibraries.gauge.enabled = false;
5283 if(typeof callback === "function")
5288 NETDATA.gaugeAnimation = function(state, status) {
5291 if(typeof status === 'boolean' && status === false)
5293 else if(typeof status === 'number')
5296 // console.log('gauge speed ' + speed);
5297 state.gauge_instance.animationSpeed = speed;
5298 state.___gaugeOld__.speed = speed;
5301 NETDATA.gaugeSet = function(state, value, min, max) {
5302 if(typeof value !== 'number') value = 0;
5303 if(typeof min !== 'number') min = 0;
5304 if(typeof max !== 'number') max = 0;
5305 if(value > max) max = value;
5306 if(value < min) min = value;
5315 // gauge.js has an issue if the needle
5316 // is smaller than min or larger than max
5317 // when we set the new values
5318 // the needle will go crazy
5320 // to prevent it, we always feed it
5321 // with a percentage, so that the needle
5322 // is always between min and max
5323 var pcent = (value - min) * 100 / (max - min);
5325 // these should never happen
5326 if(pcent < 0) pcent = 0;
5327 if(pcent > 100) pcent = 100;
5329 state.gauge_instance.set(pcent);
5330 // console.log('gauge set ' + pcent + ', value ' + value + ', min ' + min + ', max ' + max);
5332 state.___gaugeOld__.value = value;
5333 state.___gaugeOld__.min = min;
5334 state.___gaugeOld__.max = max;
5337 NETDATA.gaugeSetLabels = function(state, value, min, max) {
5338 if(state.___gaugeOld__.valueLabel !== value) {
5339 state.___gaugeOld__.valueLabel = value;
5340 state.gaugeChartLabel.innerHTML = state.legendFormatValue(value);
5342 if(state.___gaugeOld__.minLabel !== min) {
5343 state.___gaugeOld__.minLabel = min;
5344 state.gaugeChartMin.innerHTML = state.legendFormatValue(min);
5346 if(state.___gaugeOld__.maxLabel !== max) {
5347 state.___gaugeOld__.maxLabel = max;
5348 state.gaugeChartMax.innerHTML = state.legendFormatValue(max);
5352 NETDATA.gaugeClearSelection = function(state) {
5353 if(typeof state.gaugeEvent !== 'undefined') {
5354 if(state.gaugeEvent.timer !== null)
5355 clearTimeout(state.gaugeEvent.timer);
5357 state.gaugeEvent.timer = null;
5360 if(state.isAutoRefreshable() === true && state.data !== null) {
5361 NETDATA.gaugeChartUpdate(state, state.data);
5364 NETDATA.gaugeAnimation(state, false);
5365 NETDATA.gaugeSet(state, null, null, null);
5366 NETDATA.gaugeSetLabels(state, null, null, null);
5369 NETDATA.gaugeAnimation(state, true);
5373 NETDATA.gaugeSetSelection = function(state, t) {
5374 if(state.timeIsVisible(t) !== true)
5375 return NETDATA.gaugeClearSelection(state);
5377 var slot = state.calculateRowForTime(t);
5378 if(slot < 0 || slot >= state.data.result.length)
5379 return NETDATA.gaugeClearSelection(state);
5381 if(typeof state.gaugeEvent === 'undefined') {
5382 state.gaugeEvent = {
5390 var value = state.data.result[state.data.result.length - 1 - slot];
5391 var min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5392 var max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5394 // make sure it is zero based
5395 if(min > 0) min = 0;
5396 if(max < 0) max = 0;
5398 // make sure zero is in the middle
5399 if(min < 0 && max > 0) {
5401 if(min > max) max = min;
5405 state.gaugeEvent.value = value;
5406 state.gaugeEvent.min = min;
5407 state.gaugeEvent.max = max;
5408 NETDATA.gaugeSetLabels(state, value, min, max);
5410 if(state.gaugeEvent.timer === null) {
5411 NETDATA.gaugeAnimation(state, false);
5413 state.gaugeEvent.timer = setTimeout(function() {
5414 state.gaugeEvent.timer = null;
5415 NETDATA.gaugeSet(state, state.gaugeEvent.value, state.gaugeEvent.min, state.gaugeEvent.max);
5416 }, NETDATA.options.current.charts_selection_animation_delay);
5422 NETDATA.gaugeChartUpdate = function(state, data) {
5423 var value, min, max;
5425 if(NETDATA.globalPanAndZoom.isActive() === true || state.isAutoRefreshable() === false) {
5429 NETDATA.gaugeSetLabels(state, null, null, null);
5432 value = data.result[0];
5433 min = (state.gaugeMin === null)?NETDATA.commonMin.get(state):state.gaugeMin;
5434 max = (state.gaugeMax === null)?NETDATA.commonMax.get(state):state.gaugeMax;
5435 if(value < min) min = value;
5436 if(value > max) max = value;
5438 // make sure it is zero based
5439 if(min > 0) min = 0;
5440 if(max < 0) max = 0;
5442 // make sure zero is in the middle
5443 if(min < 0 && max > 0) {
5445 if(min > max) max = min;
5449 NETDATA.gaugeSetLabels(state, value, min, max);
5452 NETDATA.gaugeSet(state, value, min, max);
5456 NETDATA.gaugeChartCreate = function(state, data) {
5457 var self = $(state.element);
5458 // var chart = $(state.element_chart);
5460 var value = data.result[0];
5461 var min = self.data('gauge-min-value') || null;
5462 var max = self.data('gauge-max-value') || null;
5463 var adjust = self.data('gauge-adjust') || null;
5464 var pointerColor = self.data('gauge-pointer-color') || NETDATA.themes.current.gauge_pointer;
5465 var strokeColor = self.data('gauge-stroke-color') || NETDATA.themes.current.gauge_stroke;
5466 var startColor = self.data('gauge-start-color') || state.chartColors()[0];
5467 var stopColor = self.data('gauge-stop-color') || void 0;
5468 var generateGradient = self.data('gauge-generate-gradient') || false;
5471 min = NETDATA.commonMin.get(state);
5472 state.gaugeMin = null;
5475 state.gaugeMin = min;
5478 max = NETDATA.commonMax.get(state);
5479 state.gaugeMax = null;
5482 state.gaugeMax = max;
5484 // make sure it is zero based
5485 if(min > 0) min = 0;
5486 if(max < 0) max = 0;
5488 // make sure zero is in the middle
5489 if(min < 0 && max > 0) {
5491 if(min > max) max = min;
5495 var width = state.chartWidth(), height = state.chartHeight(); //, ratio = 1.5;
5497 // case 'width': width = height * ratio; break;
5499 // default: height = width / ratio; break;
5501 //state.element.style.width = width.toString() + 'px';
5502 //state.element.style.height = height.toString() + 'px';
5507 lines: 12, // The number of lines to draw
5508 angle: 0.15, // The length of each line
5509 lineWidth: 0.44, // 0.44 The line thickness
5511 length: 0.8, // 0.9 The radius of the inner circle
5512 strokeWidth: 0.035, // The rotation offset
5513 color: pointerColor // Fill color
5515 colorStart: startColor, // Colors
5516 colorStop: stopColor, // just experiment with them
5517 strokeColor: strokeColor, // to see which ones work best for you
5519 generateGradient: (generateGradient === true)?true:false,
5523 if (generateGradient.constructor === Array) {
5525 // data-gauge-generate-gradient="[0, 50, 100]"
5526 // data-gauge-gradient-percent-color-0="#FFFFFF"
5527 // data-gauge-gradient-percent-color-50="#999900"
5528 // data-gauge-gradient-percent-color-100="#000000"
5530 options.percentColors = new Array();
5531 var len = generateGradient.length;
5533 var pcent = generateGradient[len];
5534 var color = self.data('gauge-gradient-percent-color-' + pcent.toString()) || false;
5535 if(color !== false) {
5536 var a = new Array();
5539 options.percentColors.unshift(a);
5542 if(options.percentColors.length === 0)
5543 delete options.percentColors;
5545 else if(generateGradient === false && NETDATA.themes.current.gauge_gradient === true) {
5546 options.percentColors = [
5547 [0.0, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 0))],
5548 [0.1, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 1))],
5549 [0.2, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 2))],
5550 [0.3, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 3))],
5551 [0.4, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 4))],
5552 [0.5, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 5))],
5553 [0.6, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 6))],
5554 [0.7, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 7))],
5555 [0.8, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 8))],
5556 [0.9, NETDATA.colorLuminance(startColor, (lum_d * 10) - (lum_d * 9))],
5557 [1.0, NETDATA.colorLuminance(startColor, 0.0)]];
5560 state.gauge_canvas = document.createElement('canvas');
5561 state.gauge_canvas.id = 'gauge-' + state.uuid + '-canvas';
5562 state.gauge_canvas.className = 'gaugeChart';
5563 state.gauge_canvas.width = width;
5564 state.gauge_canvas.height = height;
5565 state.element_chart.appendChild(state.gauge_canvas);
5567 var valuefontsize = Math.floor(height / 6);
5568 var valuetop = Math.round((height - valuefontsize - (height / 6)) / 2);
5569 state.gaugeChartLabel = document.createElement('span');
5570 state.gaugeChartLabel.className = 'gaugeChartLabel';
5571 state.gaugeChartLabel.style.fontSize = valuefontsize + 'px';
5572 state.gaugeChartLabel.style.top = valuetop.toString() + 'px';
5573 state.element_chart.appendChild(state.gaugeChartLabel);
5575 var titlefontsize = Math.round(valuefontsize / 2);
5577 state.gaugeChartTitle = document.createElement('span');
5578 state.gaugeChartTitle.className = 'gaugeChartTitle';
5579 state.gaugeChartTitle.innerHTML = state.title;
5580 state.gaugeChartTitle.style.fontSize = titlefontsize + 'px';
5581 state.gaugeChartTitle.style.lineHeight = titlefontsize + 'px';
5582 state.gaugeChartTitle.style.top = titletop.toString() + 'px';
5583 state.element_chart.appendChild(state.gaugeChartTitle);
5585 var unitfontsize = Math.round(titlefontsize * 0.9);
5586 state.gaugeChartUnits = document.createElement('span');
5587 state.gaugeChartUnits.className = 'gaugeChartUnits';
5588 state.gaugeChartUnits.innerHTML = state.units;
5589 state.gaugeChartUnits.style.fontSize = unitfontsize + 'px';
5590 state.element_chart.appendChild(state.gaugeChartUnits);
5592 state.gaugeChartMin = document.createElement('span');
5593 state.gaugeChartMin.className = 'gaugeChartMin';
5594 state.gaugeChartMin.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5595 state.element_chart.appendChild(state.gaugeChartMin);
5597 state.gaugeChartMax = document.createElement('span');
5598 state.gaugeChartMax.className = 'gaugeChartMax';
5599 state.gaugeChartMax.style.fontSize = Math.round(valuefontsize * 0.75).toString() + 'px';
5600 state.element_chart.appendChild(state.gaugeChartMax);
5602 // when we just re-create the chart
5603 // do not animate the first update
5605 if(typeof state.gauge_instance !== 'undefined')
5608 state.gauge_instance = new Gauge(state.gauge_canvas).setOptions(options); // create sexy gauge!
5610 state.___gaugeOld__ = {
5619 // we will always feed a percentage
5620 state.gauge_instance.minValue = 0;
5621 state.gauge_instance.maxValue = 100;
5623 NETDATA.gaugeAnimation(state, animate);
5624 NETDATA.gaugeSet(state, value, min, max);
5625 NETDATA.gaugeSetLabels(state, value, min, max);
5626 NETDATA.gaugeAnimation(state, true);
5630 // ----------------------------------------------------------------------------------------------------------------
5631 // Charts Libraries Registration
5633 NETDATA.chartLibraries = {
5635 initialize: NETDATA.dygraphInitialize,
5636 create: NETDATA.dygraphChartCreate,
5637 update: NETDATA.dygraphChartUpdate,
5638 resize: function(state) {
5639 if(typeof state.dygraph_instance.resize === 'function')
5640 state.dygraph_instance.resize();
5642 setSelection: NETDATA.dygraphSetSelection,
5643 clearSelection: NETDATA.dygraphClearSelection,
5644 toolboxPanAndZoom: NETDATA.dygraphToolboxPanAndZoom,
5647 format: function(state) { return 'json'; },
5648 options: function(state) { return 'ms|flip'; },
5649 legend: function(state) {
5650 if(this.isSparkline(state) === false)
5651 return 'right-side';
5655 autoresize: function(state) { return true; },
5656 max_updates_to_recreate: function(state) { return 5000; },
5657 track_colors: function(state) { return true; },
5658 pixels_per_point: function(state) {
5659 if(this.isSparkline(state) === false)
5665 isSparkline: function(state) {
5666 if(typeof state.dygraph_sparkline === 'undefined') {
5667 var t = $(state.element).data('dygraph-theme');
5668 if(t === 'sparkline')
5669 state.dygraph_sparkline = true;
5671 state.dygraph_sparkline = false;
5673 return state.dygraph_sparkline;
5677 initialize: NETDATA.sparklineInitialize,
5678 create: NETDATA.sparklineChartCreate,
5679 update: NETDATA.sparklineChartUpdate,
5681 setSelection: undefined, // function(state, t) { return true; },
5682 clearSelection: undefined, // function(state) { return true; },
5683 toolboxPanAndZoom: null,
5686 format: function(state) { return 'array'; },
5687 options: function(state) { return 'flip|abs'; },
5688 legend: function(state) { return null; },
5689 autoresize: function(state) { return false; },
5690 max_updates_to_recreate: function(state) { return 5000; },
5691 track_colors: function(state) { return false; },
5692 pixels_per_point: function(state) { return 3; }
5695 initialize: NETDATA.peityInitialize,
5696 create: NETDATA.peityChartCreate,
5697 update: NETDATA.peityChartUpdate,
5699 setSelection: undefined, // function(state, t) { return true; },
5700 clearSelection: undefined, // function(state) { return true; },
5701 toolboxPanAndZoom: null,
5704 format: function(state) { return 'ssvcomma'; },
5705 options: function(state) { return 'null2zero|flip|abs'; },
5706 legend: function(state) { return null; },
5707 autoresize: function(state) { return false; },
5708 max_updates_to_recreate: function(state) { return 5000; },
5709 track_colors: function(state) { return false; },
5710 pixels_per_point: function(state) { return 3; }
5713 initialize: NETDATA.morrisInitialize,
5714 create: NETDATA.morrisChartCreate,
5715 update: NETDATA.morrisChartUpdate,
5717 setSelection: undefined, // function(state, t) { return true; },
5718 clearSelection: undefined, // function(state) { return true; },
5719 toolboxPanAndZoom: null,
5722 format: function(state) { return 'json'; },
5723 options: function(state) { return 'objectrows|ms'; },
5724 legend: function(state) { return null; },
5725 autoresize: function(state) { return false; },
5726 max_updates_to_recreate: function(state) { return 50; },
5727 track_colors: function(state) { return false; },
5728 pixels_per_point: function(state) { return 15; }
5731 initialize: NETDATA.googleInitialize,
5732 create: NETDATA.googleChartCreate,
5733 update: NETDATA.googleChartUpdate,
5735 setSelection: undefined, //function(state, t) { return true; },
5736 clearSelection: undefined, //function(state) { return true; },
5737 toolboxPanAndZoom: null,
5740 format: function(state) { return 'datatable'; },
5741 options: function(state) { return ''; },
5742 legend: function(state) { return null; },
5743 autoresize: function(state) { return false; },
5744 max_updates_to_recreate: function(state) { return 300; },
5745 track_colors: function(state) { return false; },
5746 pixels_per_point: function(state) { return 4; }
5749 initialize: NETDATA.raphaelInitialize,
5750 create: NETDATA.raphaelChartCreate,
5751 update: NETDATA.raphaelChartUpdate,
5753 setSelection: undefined, // function(state, t) { return true; },
5754 clearSelection: undefined, // function(state) { return true; },
5755 toolboxPanAndZoom: null,
5758 format: function(state) { return 'json'; },
5759 options: function(state) { return ''; },
5760 legend: function(state) { return null; },
5761 autoresize: function(state) { return false; },
5762 max_updates_to_recreate: function(state) { return 5000; },
5763 track_colors: function(state) { return false; },
5764 pixels_per_point: function(state) { return 3; }
5767 initialize: NETDATA.c3Initialize,
5768 create: NETDATA.c3ChartCreate,
5769 update: NETDATA.c3ChartUpdate,
5771 setSelection: undefined, // function(state, t) { return true; },
5772 clearSelection: undefined, // function(state) { return true; },
5773 toolboxPanAndZoom: null,
5776 format: function(state) { return 'csvjsonarray'; },
5777 options: function(state) { return 'milliseconds'; },
5778 legend: function(state) { return null; },
5779 autoresize: function(state) { return false; },
5780 max_updates_to_recreate: function(state) { return 5000; },
5781 track_colors: function(state) { return false; },
5782 pixels_per_point: function(state) { return 15; }
5785 initialize: NETDATA.d3Initialize,
5786 create: NETDATA.d3ChartCreate,
5787 update: NETDATA.d3ChartUpdate,
5789 setSelection: undefined, // function(state, t) { return true; },
5790 clearSelection: undefined, // function(state) { return true; },
5791 toolboxPanAndZoom: null,
5794 format: function(state) { return 'json'; },
5795 options: function(state) { return ''; },
5796 legend: function(state) { return null; },
5797 autoresize: function(state) { return false; },
5798 max_updates_to_recreate: function(state) { return 5000; },
5799 track_colors: function(state) { return false; },
5800 pixels_per_point: function(state) { return 3; }
5803 initialize: NETDATA.easypiechartInitialize,
5804 create: NETDATA.easypiechartChartCreate,
5805 update: NETDATA.easypiechartChartUpdate,
5807 setSelection: NETDATA.easypiechartSetSelection,
5808 clearSelection: NETDATA.easypiechartClearSelection,
5809 toolboxPanAndZoom: null,
5812 format: function(state) { return 'array'; },
5813 options: function(state) { return 'absolute'; },
5814 legend: function(state) { return null; },
5815 autoresize: function(state) { return false; },
5816 max_updates_to_recreate: function(state) { return 5000; },
5817 track_colors: function(state) { return true; },
5818 pixels_per_point: function(state) { return 3; },
5822 initialize: NETDATA.gaugeInitialize,
5823 create: NETDATA.gaugeChartCreate,
5824 update: NETDATA.gaugeChartUpdate,
5826 setSelection: NETDATA.gaugeSetSelection,
5827 clearSelection: NETDATA.gaugeClearSelection,
5828 toolboxPanAndZoom: null,
5831 format: function(state) { return 'array'; },
5832 options: function(state) { return 'absolute'; },
5833 legend: function(state) { return null; },
5834 autoresize: function(state) { return false; },
5835 max_updates_to_recreate: function(state) { return 5000; },
5836 track_colors: function(state) { return true; },
5837 pixels_per_point: function(state) { return 3; },
5842 NETDATA.registerChartLibrary = function(library, url) {
5843 if(NETDATA.options.debug.libraries === true)
5844 console.log("registering chart library: " + library);
5846 NETDATA.chartLibraries[library].url = url;
5847 NETDATA.chartLibraries[library].initialized = true;
5848 NETDATA.chartLibraries[library].enabled = true;
5851 // ----------------------------------------------------------------------------------------------------------------
5852 // Load required JS libraries and CSS
5854 NETDATA.requiredJs = [
5856 url: NETDATA.serverDefault + 'lib/bootstrap-3.3.7.min.js',
5858 isAlreadyLoaded: function() {
5859 // check if bootstrap is loaded
5860 if(typeof $().emulateTransitionEnd == 'function')
5863 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5871 url: NETDATA.serverDefault + 'lib/jquery.nanoscroller-0.8.7.min.js',
5872 isAlreadyLoaded: function() { return false; }
5876 NETDATA.requiredCSS = [
5878 url: NETDATA.themes.current.bootstrap_css,
5879 isAlreadyLoaded: function() {
5880 if(typeof netdataNoBootstrap !== 'undefined' && netdataNoBootstrap)
5887 url: NETDATA.serverDefault + 'css/font-awesome.min.css?v4.7.0',
5888 isAlreadyLoaded: function() { return false; }
5891 url: NETDATA.themes.current.dashboard_css,
5892 isAlreadyLoaded: function() { return false; }
5896 NETDATA.loadedRequiredJs = 0;
5897 NETDATA.loadRequiredJs = function(index, callback) {
5898 if(index >= NETDATA.requiredJs.length) {
5899 if(typeof callback === 'function')
5904 if(NETDATA.requiredJs[index].isAlreadyLoaded()) {
5905 NETDATA.loadedRequiredJs++;
5906 NETDATA.loadRequiredJs(++index, callback);
5910 if(NETDATA.options.debug.main_loop === true)
5911 console.log('loading ' + NETDATA.requiredJs[index].url);
5914 if(typeof NETDATA.requiredJs[index].async !== 'undefined' && NETDATA.requiredJs[index].async === false)
5918 url: NETDATA.requiredJs[index].url,
5921 xhrFields: { withCredentials: true } // required for the cookie
5924 if(NETDATA.options.debug.main_loop === true)
5925 console.log('loaded ' + NETDATA.requiredJs[index].url);
5928 alert('Cannot load required JS library: ' + NETDATA.requiredJs[index].url);
5930 .always(function() {
5931 NETDATA.loadedRequiredJs++;
5934 NETDATA.loadRequiredJs(++index, callback);
5938 NETDATA.loadRequiredJs(++index, callback);
5941 NETDATA.loadRequiredCSS = function(index) {
5942 if(index >= NETDATA.requiredCSS.length)
5945 if(NETDATA.requiredCSS[index].isAlreadyLoaded()) {
5946 NETDATA.loadRequiredCSS(++index);
5950 if(NETDATA.options.debug.main_loop === true)
5951 console.log('loading ' + NETDATA.requiredCSS[index].url);
5953 NETDATA._loadCSS(NETDATA.requiredCSS[index].url);
5954 NETDATA.loadRequiredCSS(++index);
5958 // ----------------------------------------------------------------------------------------------------------------
5959 // Registry of netdata hosts
5962 onclick: null, // the callback to handle the click - it will be called with the alarm log entry
5963 chart_div_offset: 100, // give that space above the chart when scrolling to it
5964 chart_div_id_prefix: 'chart_', // the chart DIV IDs have this prefix (they should be NETDATA.name2id(chart.id))
5965 chart_div_animation_duration: 0,// the duration of the animation while scrolling to a chart
5967 ms_penalty: 0, // the time penalty of the next alarm
5968 ms_between_notifications: 500, // firefox moves the alarms off-screen (above, outside the top of the screen)
5969 // if alarms are shown faster than: one per 500ms
5971 notifications: false, // when true, the browser supports notifications (may not be granted though)
5972 last_notification_id: 0, // the id of the last alarm_log we have raised an alarm for
5973 first_notification_id: 0, // the id of the first alarm_log entry for this session
5974 // this is used to prevent CLEAR notifications for past events
5975 // notifications_shown: new Array(),
5977 server: null, // the server to connect to for fetching alarms
5978 current: null, // the list of raised alarms - updated in the background
5979 callback: null, // a callback function to call every time the list of raised alarms is refreshed
5981 notify: function(entry) {
5982 // console.log('alarm ' + entry.unique_id);
5984 if(entry.updated === true) {
5985 // console.log('alarm ' + entry.unique_id + ' has been updated by another alarm');
5989 var value = entry.value;
5990 if(NETDATA.alarms.current !== null) {
5991 var t = NETDATA.alarms.current.alarms[entry.chart + '.' + entry.name];
5992 if(typeof t !== 'undefined' && entry.status == t.status)
5996 var name = entry.name.replace(/_/g, ' ');
5997 var status = entry.status.toLowerCase();
5998 var title = name + ' = ' + ((value === null)?'NaN':Math.floor(value)).toString() + ' ' + entry.units;
5999 var tag = entry.alarm_id;
6000 var icon = 'images/seo-performance-128.png';
6001 var interaction = false;
6005 // console.log('alarm ' + entry.unique_id + ' ' + entry.chart + '.' + entry.name + ' is ' + entry.status);
6007 switch(entry.status) {
6015 case 'UNINITIALIZED':
6019 if(entry.unique_id < NETDATA.alarms.first_notification_id) {
6020 // console.log('alarm ' + entry.unique_id + ' is not current');
6023 if(entry.old_status === 'UNINITIALIZED' || entry.old_status === 'UNDEFINED') {
6024 // console.log('alarm' + entry.unique_id + ' switch to CLEAR from ' + entry.old_status);
6027 title = name + ' back to normal';
6028 icon = 'images/check-mark-2-128-green.png'
6029 interaction = false;
6033 if(entry.old_status === 'CRITICAL')
6034 status = 'demoted to ' + entry.status.toLowerCase();
6036 icon = 'images/alert-128-orange.png';
6037 interaction = false;
6041 if(entry.old_status === 'WARNING')
6042 status = 'escalated to ' + entry.status.toLowerCase();
6044 icon = 'images/alert-128-red.png'
6049 console.log('invalid alarm status ' + entry.status);
6054 // cleanup old notifications with the same alarm_id as this one
6055 // FIXME: it does not seem to work on any web browser!
6056 var len = NETDATA.alarms.notifications_shown.length;
6058 var n = NETDATA.alarms.notifications_shown[len];
6059 if(n.data.alarm_id === entry.alarm_id) {
6060 console.log('removing old alarm ' + n.data.unique_id);
6062 // close the notification
6065 // remove it from the array
6066 NETDATA.alarms.notifications_shown.splice(len, 1);
6067 len = NETDATA.alarms.notifications_shown.length;
6074 setTimeout(function() {
6075 // show this notification
6076 // console.log('new notification: ' + title);
6077 var n = new Notification(title, {
6078 body: entry.hostname + ' - ' + entry.chart + ' (' + entry.family + ') - ' + status + ': ' + entry.info,
6080 requireInteraction: interaction,
6081 icon: NETDATA.serverDefault + icon,
6085 n.onclick = function(event) {
6086 event.preventDefault();
6087 NETDATA.alarms.onclick(event.target.data);
6091 // NETDATA.alarms.notifications_shown.push(n);
6092 // console.log(entry);
6093 }, NETDATA.alarms.ms_penalty);
6095 NETDATA.alarms.ms_penalty += NETDATA.alarms.ms_between_notifications;
6099 scrollToChart: function(chart_id) {
6100 if(typeof chart_id === 'string') {
6101 var offset = $('#' + NETDATA.alarms.chart_div_id_prefix + NETDATA.name2id(chart_id)).offset();
6102 if(typeof offset !== 'undefined') {
6103 $('html, body').animate({ scrollTop: offset.top - NETDATA.alarms.chart_div_offset }, NETDATA.alarms.chart_div_animation_duration);
6110 scrollToAlarm: function(alarm) {
6111 if(typeof alarm === 'object') {
6112 var ret = NETDATA.alarms.scrollToChart(alarm.chart);
6114 if(ret === true && NETDATA.options.page_is_visible === false)
6116 // 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.');
6121 notifyAll: function() {
6122 // console.log('FETCHING ALARM LOG');
6123 NETDATA.alarms.get_log(NETDATA.alarms.last_notification_id, function(data) {
6124 // console.log('ALARM LOG FETCHED');
6126 if(data === null || typeof data !== 'object') {
6127 console.log('invalid alarms log response');
6131 if(data.length === 0) {
6132 console.log('received empty alarm log');
6136 // console.log('received alarm log of ' + data.length + ' entries, from ' + data[data.length - 1].unique_id.toString() + ' to ' + data[0].unique_id.toString());
6138 data.sort(function(a, b) {
6139 if(a.unique_id > b.unique_id) return -1;
6140 if(a.unique_id < b.unique_id) return 1;
6144 NETDATA.alarms.ms_penalty = 0;
6146 var len = data.length;
6148 if(data[len].unique_id > NETDATA.alarms.last_notification_id) {
6149 NETDATA.alarms.notify(data[len]);
6152 // console.log('ignoring alarm (older) with id ' + data[len].unique_id.toString());
6155 NETDATA.alarms.last_notification_id = data[0].unique_id;
6156 NETDATA.localStorageSet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6157 // console.log('last notification id = ' + NETDATA.alarms.last_notification_id);
6161 check_notifications: function() {
6162 // returns true if we should fire 1+ notifications
6164 if(NETDATA.alarms.notifications !== true) {
6165 // console.log('notifications not available');
6169 if(Notification.permission !== 'granted') {
6170 // console.log('notifications not granted');
6174 if(typeof NETDATA.alarms.current !== 'undefined' && typeof NETDATA.alarms.current.alarms === 'object') {
6175 // console.log('can do alarms: old id = ' + NETDATA.alarms.last_notification_id + ' new id = ' + NETDATA.alarms.current.latest_alarm_log_unique_id);
6177 if(NETDATA.alarms.current.latest_alarm_log_unique_id > NETDATA.alarms.last_notification_id) {
6178 // console.log('new alarms detected');
6181 //else console.log('no new alarms');
6183 // else console.log('cannot process alarms');
6188 get: function(what, callback) {
6190 url: NETDATA.alarms.server + '/api/v1/alarms?' + what.toString(),
6194 'Cache-Control': 'no-cache, no-store',
6195 'Pragma': 'no-cache'
6197 xhrFields: { withCredentials: true } // required for the cookie
6199 .done(function(data) {
6200 if(NETDATA.alarms.first_notification_id === 0 && typeof data.latest_alarm_log_unique_id === 'number')
6201 NETDATA.alarms.first_notification_id = data.latest_alarm_log_unique_id;
6203 if(typeof callback === 'function')
6207 NETDATA.error(415, NETDATA.alarms.server);
6209 if(typeof callback === 'function')
6214 update_forever: function() {
6215 NETDATA.alarms.get('active', function(data) {
6217 NETDATA.alarms.current = data;
6219 if(NETDATA.alarms.check_notifications() === true) {
6220 NETDATA.alarms.notifyAll();
6223 if (typeof NETDATA.alarms.callback === 'function') {
6224 NETDATA.alarms.callback(data);
6227 // Health monitoring is disabled on this netdata
6228 if(data.status === false) return;
6231 setTimeout(NETDATA.alarms.update_forever, 10000);
6235 get_log: function(last_id, callback) {
6236 // console.log('fetching all log after ' + last_id.toString());
6238 url: NETDATA.alarms.server + '/api/v1/alarm_log?after=' + last_id.toString(),
6242 'Cache-Control': 'no-cache, no-store',
6243 'Pragma': 'no-cache'
6245 xhrFields: { withCredentials: true } // required for the cookie
6247 .done(function(data) {
6248 if(typeof callback === 'function')
6252 NETDATA.error(416, NETDATA.alarms.server);
6254 if(typeof callback === 'function')
6260 var host = NETDATA.serverDefault;
6261 while(host.slice(-1) === '/')
6262 host = host.substring(0, host.length - 1);
6263 NETDATA.alarms.server = host;
6265 NETDATA.alarms.last_notification_id = NETDATA.localStorageGet('last_notification_id', NETDATA.alarms.last_notification_id, null);
6267 if(NETDATA.alarms.onclick === null)
6268 NETDATA.alarms.onclick = NETDATA.alarms.scrollToAlarm;
6270 if(netdataShowAlarms === true) {
6271 NETDATA.alarms.update_forever();
6273 if('Notification' in window) {
6274 // console.log('notifications available');
6275 NETDATA.alarms.notifications = true;
6277 if(Notification.permission === 'default')
6278 Notification.requestPermission();
6284 // ----------------------------------------------------------------------------------------------------------------
6285 // Registry of netdata hosts
6287 NETDATA.registry = {
6288 server: null, // the netdata registry server
6289 person_guid: null, // the unique ID of this browser / user
6290 machine_guid: null, // the unique ID the netdata server that served dashboard.js
6291 hostname: null, // the hostname of the netdata server that served dashboard.js
6292 machines: null, // the user's other URLs
6293 machines_array: null, // the user's other URLs in an array
6296 parsePersonUrls: function(person_urls) {
6297 // console.log(person_urls);
6298 NETDATA.registry.person_urls = person_urls;
6301 NETDATA.registry.machines = {};
6302 NETDATA.registry.machines_array = new Array();
6304 var now = Date.now();
6305 var apu = person_urls;
6308 if(typeof NETDATA.registry.machines[apu[i][0]] === 'undefined') {
6309 // console.log('adding: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6315 accesses: apu[i][3],
6317 alternate_urls: new Array()
6319 obj.alternate_urls.push(apu[i][1]);
6321 NETDATA.registry.machines[apu[i][0]] = obj;
6322 NETDATA.registry.machines_array.push(obj);
6325 // console.log('appending: ' + apu[i][4] + ', ' + ((now - apu[i][2]) / 1000).toString());
6327 var pu = NETDATA.registry.machines[apu[i][0]];
6328 if(pu.last_t < apu[i][2]) {
6330 pu.last_t = apu[i][2];
6331 pu.name = apu[i][4];
6333 pu.accesses += apu[i][3];
6334 pu.alternate_urls.push(apu[i][1]);
6339 if(typeof netdataRegistryCallback === 'function')
6340 netdataRegistryCallback(NETDATA.registry.machines_array);
6344 if(netdataRegistry !== true) return;
6346 NETDATA.registry.hello(NETDATA.serverDefault, function(data) {
6348 NETDATA.registry.server = data.registry;
6349 NETDATA.registry.machine_guid = data.machine_guid;
6350 NETDATA.registry.hostname = data.hostname;
6352 NETDATA.registry.access(2, function (person_urls) {
6353 NETDATA.registry.parsePersonUrls(person_urls);
6360 hello: function(host, callback) {
6361 while(host.slice(-1) === '/')
6362 host = host.substring(0, host.length - 1);
6364 // send HELLO to a netdata server:
6365 // 1. verifies the server is reachable
6366 // 2. responds with the registry URL, the machine GUID of this netdata server and its hostname
6368 url: host + '/api/v1/registry?action=hello',
6372 'Cache-Control': 'no-cache, no-store',
6373 'Pragma': 'no-cache'
6375 xhrFields: { withCredentials: true } // required for the cookie
6377 .done(function(data) {
6378 if(typeof data.status !== 'string' || data.status !== 'ok') {
6379 NETDATA.error(408, host + ' response: ' + JSON.stringify(data));
6383 if(typeof callback === 'function')
6387 NETDATA.error(407, host);
6389 if(typeof callback === 'function')
6394 access: function(max_redirects, callback) {
6395 // send ACCESS to a netdata registry:
6396 // 1. it lets it know we are accessing a netdata server (its machine GUID and its URL)
6397 // 2. it responds with a list of netdata servers we know
6398 // the registry identifies us using a cookie it sets the first time we access it
6399 // the registry may respond with a redirect URL to send us to another registry
6401 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),
6405 'Cache-Control': 'no-cache, no-store',
6406 'Pragma': 'no-cache'
6408 xhrFields: { withCredentials: true } // required for the cookie
6410 .done(function(data) {
6411 var redirect = null;
6412 if(typeof data.registry === 'string')
6413 redirect = data.registry;
6415 if(typeof data.status !== 'string' || data.status !== 'ok') {
6416 NETDATA.error(409, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6421 if(redirect !== null && max_redirects > 0) {
6422 NETDATA.registry.server = redirect;
6423 NETDATA.registry.access(max_redirects - 1, callback);
6426 if(typeof callback === 'function')
6431 if(typeof data.person_guid === 'string')
6432 NETDATA.registry.person_guid = data.person_guid;
6434 if(typeof callback === 'function')
6435 callback(data.urls);
6439 NETDATA.error(410, NETDATA.registry.server);
6441 if(typeof callback === 'function')
6446 delete: function(delete_url, callback) {
6447 // send DELETE to a netdata registry:
6449 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),
6453 'Cache-Control': 'no-cache, no-store',
6454 'Pragma': 'no-cache'
6456 xhrFields: { withCredentials: true } // required for the cookie
6458 .done(function(data) {
6459 if(typeof data.status !== 'string' || data.status !== 'ok') {
6460 NETDATA.error(411, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6464 if(typeof callback === 'function')
6468 NETDATA.error(412, NETDATA.registry.server);
6470 if(typeof callback === 'function')
6475 search: function(machine_guid, callback) {
6476 // SEARCH for the URLs of a machine:
6478 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,
6482 'Cache-Control': 'no-cache, no-store',
6483 'Pragma': 'no-cache'
6485 xhrFields: { withCredentials: true } // required for the cookie
6487 .done(function(data) {
6488 if(typeof data.status !== 'string' || data.status !== 'ok') {
6489 NETDATA.error(417, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6493 if(typeof callback === 'function')
6497 NETDATA.error(418, NETDATA.registry.server);
6499 if(typeof callback === 'function')
6504 switch: function(new_person_guid, callback) {
6507 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,
6511 'Cache-Control': 'no-cache, no-store',
6512 'Pragma': 'no-cache'
6514 xhrFields: { withCredentials: true } // required for the cookie
6516 .done(function(data) {
6517 if(typeof data.status !== 'string' || data.status !== 'ok') {
6518 NETDATA.error(413, NETDATA.registry.server + ' responded with: ' + JSON.stringify(data));
6522 if(typeof callback === 'function')
6526 NETDATA.error(414, NETDATA.registry.server);
6528 if(typeof callback === 'function')
6534 // ----------------------------------------------------------------------------------------------------------------
6537 if(typeof netdataPrepCallback === 'function')
6538 netdataPrepCallback();
6540 NETDATA.errorReset();
6541 NETDATA.loadRequiredCSS(0);
6543 NETDATA._loadjQuery(function() {
6544 NETDATA.loadRequiredJs(0, function() {
6545 if(typeof $().emulateTransitionEnd !== 'function') {
6546 // bootstrap is not available
6547 NETDATA.options.current.show_help = false;
6550 if(typeof netdataDontStart === 'undefined' || !netdataDontStart) {
6551 if(NETDATA.options.debug.main_loop === true)
6552 console.log('starting chart refresh thread');
6559 // window.NETDATA = NETDATA;
6560 // })(window, document);