]> arthur.barton.de Git - netdata.git/blob - web/index.js
added command line option -nd or -nodaemon to disable forking, #10
[netdata.git] / web / index.js
1 var page_is_visible = 1;
2
3 var TARGET_THUMB_GRAPH_WIDTH = 500;             // thumb charts width will range from 0.5 to 1.5 of that
4 var MINIMUM_THUMB_GRAPH_WIDTH = 400;    // thumb chart will generally try to be wider than that
5 var TARGET_THUMB_GRAPH_HEIGHT = 180;    // the height of the thumb charts
6
7 var THUMBS_MAX_TIME_TO_SHOW = 240;              // how much time the thumb charts will present?
8 var THUMBS_POINTS_DIVISOR = 3;
9 var THUMBS_STACKED_POINTS_DIVISOR = 3;
10
11 var GROUPS_MAX_TIME_TO_SHOW = 120;              // how much time the group charts will present?
12 var GROUPS_POINTS_DIVISOR = 2;
13 var GROUPS_STACKED_POINTS_DIVISOR = 2;
14
15 var MAINCHART_MIN_TIME_TO_SHOW = 600;   // how much time the main chart will present by default?
16 var MAINCHART_POINTS_DIVISOR = 5;               // how much detailed will the main chart be by default? 1 = finest, higher is faster
17 var MAINCHART_STACKED_POINTS_DIVISOR = 10;              // how much detailed will the main chart be by default? 1 = finest, higher is faster
18
19 var MAINCHART_CONTROL_HEIGHT = 75;              // how tall the control chart will be
20 var MAINCHART_CONTROL_DIVISOR = 2;              // how much detailed will the control chart be? 1 = finest, higher is faster
21 var MAINCHART_INITIAL_SELECTOR= 20;             // 1/20th of the width, this overrides MAINCHART_MIN_TIME_TO_SHOW
22
23 var CHARTS_REFRESH_LOOP = 50;                   // delay between chart refreshes
24 var CHARTS_REFRESH_IDLE = 500;                  // delay between chart refreshes when no chart was ready for refresh the last time
25 var CHARTS_CHECK_NO_FOCUS = 500;                // delay to check for visibility when the page has no focus
26 var CHARTS_SCROLL_IDLE = 100;                   // delay to wait after a page scroll
27
28
29 function isIE() {
30   userAgent = navigator.userAgent;
31   return userAgent.indexOf("MSIE ") > -1 || userAgent.indexOf("Trident/") > -1;
32 }
33
34 if(isIE()){
35         // do stuff with ie-users
36         CHARTS_REFRESH_LOOP=250;
37         CHARTS_SCROLL_IDLE=500;
38 }
39
40 var MODE_THUMBS = 1;
41 var MODE_MAIN = 2;
42 var MODE_GROUP_THUMBS = 3;
43 var mode; // one of the MODE_* values
44
45 var mycharts = new Array();
46 var mainchart;
47
48 // html for the main menu
49 var mainmenu = "";
50 var categoriesmainmenu = "";
51 var familiesmainmenu = "";
52 var chartsmainmenu = "";
53
54
55 // ------------------------------------------------------------------------
56 // common HTML generation
57
58 function thumbChartActions(i, c, nogroup) {
59         var name = c.name;
60         if(!nogroup) name = c.family;
61
62         var refinfo = "the chart is drawing ";
63         if(c.group == 1) refinfo += "every single point collected (" + c.update_every + "s each).";
64         else refinfo += ((c.group_method == "average")?"the average":"the max") + " value for every " + (c.group * c.update_every) + " seconds of data";
65
66         var html = "<div class=\"btn-group btn-group\" data-toggle=\"tooltip\" title=\"" + refinfo + "\">"
67         +               "<button type=\"button\" class=\"btn btn-default\" onclick=\"javascript: return;\"><span class=\"glyphicon glyphicon-info-sign\"></span></button>"
68         +       "</div>"
69         +       "<div class=\"btn-group btn-group\"><button type=\"button\" class=\"btn btn-default disabled\"><small>&nbsp;&nbsp; " + name + "</small></button>";
70
71         if(!nogroup) {
72                 var ingroup = 0;
73                 var ingroup_detail = 0;
74
75                 $.each(mycharts, function(i, d) {
76                         if(d.family == c.family) {
77                                 ingroup++;
78                                 if(d.isdetail) ingroup_detail++;
79                         }
80                 });
81
82                 var hidden = "";
83                 if(ingroup_detail) hidden = ", including " + ingroup_detail + " charts not shown now";
84
85                 html += "<button type=\"button\" data-toggle=\"tooltip\" title=\"Show all " + ingroup + " charts in group '" + c.family + "'" + hidden + "\" class=\"btn btn-default\" onclick=\"initGroupGraphs('" + c.family +"');\"><span class=\"glyphicon glyphicon-th-large\"></span></button>";
86         }
87
88         html += "<button type=\"button\" data-toggle=\"tooltip\" title=\"show chart '" + c.name + "' in fullscreen\" class=\"btn btn-default\" onclick=\"initMainChartIndex(" + i +");\"><span class=\"glyphicon glyphicon-resize-full\"></span></button>"
89         +               "<button type=\"button\" data-toggle=\"tooltip\" title=\"set options for chart '" + c.name + "'\" class=\"btn btn-default disabled\" onclick=\"alert('Not implemented yet!');\"><span class=\"glyphicon glyphicon-cog\"></span></button>"
90         +               "<button type=\"button\" data-toggle=\"tooltip\" title=\"ignore chart '" + c.name + "'\" class=\"btn btn-default\" onclick=\"disableChart(" + i + ");\"><span class=\"glyphicon glyphicon-trash\"></span></button>"
91         +       "</div>";
92
93         return html;
94 }
95
96 function mylog(txt) {
97         console.log(txt);
98         $('#logline').html(txt);
99 }
100
101 function chartssort(a, b) {
102         if(a.priority == b.priority) {
103                 if(a.name < b.name) return -1;
104         }
105         else if(a.priority < b.priority) return -1;
106         
107         return 1;
108 }
109
110
111 // ------------------------------------------------------------------------
112 // MAINGRAPH = fullscreen view of 1 graph
113
114 // copy the chart c to mainchart
115 // switch to main graphs screen
116 function initMainChart(c) {
117         if(mainchart) cleanThisChart(mainchart);
118
119         mainchart = $.extend(true, {}, c);
120         mainchart.enabled = true;
121         mainchart.refreshCount = 0;
122         mainchart.last_updated = 0;
123         mainchart.chartOptions.explorer = null;
124         mainchart.chart = null;
125
126         mainchart.before = 0;
127         mainchart.after = 0;
128
129         mainchart.chartOptions.width = screenWidth();
130         mainchart.chartOptions.height = $(window).height() - 150 - MAINCHART_CONTROL_HEIGHT;
131         if(mainchart.chartOptions.height < 300) mainchart.chartOptions.height = 300;
132
133         mainchart.div = 'maingraph';
134         mainchart.max_time_to_show = (mainchart.last_entry_t - mainchart.first_entry_t) / MAINCHART_INITIAL_SELECTOR;
135         if(mainchart.max_time_to_show < MAINCHART_MIN_TIME_TO_SHOW) mainchart.max_time_to_show = MAINCHART_MIN_TIME_TO_SHOW;
136         calculateChartPointsToShow(mainchart, mainchart.chartOptions.isStacked?MAINCHART_STACKED_POINTS_DIVISOR:MAINCHART_POINTS_DIVISOR, mainchart.max_time_to_show, 0);
137
138         // copy it to the hidden chart
139         mainchart.hiddenchart = $.extend(true, {}, mainchart);
140         mainchart.hiddenchart.chartOptions.height = MAINCHART_CONTROL_HEIGHT;
141         mainchart.hiddenchart.div = 'maingraph_control';
142         mainchart.hiddenchart.non_zero = 0;
143
144         // initialize the div
145         showChartIsLoading(mainchart.div, mainchart.name, mainchart.chartOptions.width, mainchart.chartOptions.height);
146         document.getElementById(mainchart.hiddenchart.div).innerHTML = "<table><tr><td align=\"center\" width=\"" + mainchart.hiddenchart.chartOptions.width + "\" height=\"" + mainchart.hiddenchart.chartOptions.height + "\" style=\"vertical-align:middle\"><h4><span class=\"label label-default\">Please wait...</span></h4></td></tr></table>";
147         //showChartIsLoading(mainchart.hiddenchart.div, mainchart.hiddenchart.name, mainchart.hiddenchart.chartOptions.width, mainchart.hiddenchart.chartOptions.height);
148
149         // set the radio buttons
150         setMainChartGroupMethod(mainchart.group_method, 'no-refresh');
151         setMainChartMax('normal');
152
153         $('#group' + mainchart.group).trigger('click');
154         setMainChartGroup(mainchart.group, 'no-refresh');
155
156         switchToMainGraph();
157 }
158
159 function refreshHiddenChart(doNext) {
160         if(refresh_mode == REFRESH_PAUSED && mainchart.hiddenchart.last_updated != 0) {
161                 if(typeof doNext == "function") doNext();
162                 return;
163         }
164
165         // is it too soon for a refresh?
166         var now = new Date().getTime();
167         if((now - mainchart.hiddenchart.last_updated) < (mainchart.update_every * 10 * 1000) || (now - mainchart.hiddenchart.last_updated) < (mainchart.hiddenchart.group * mainchart.hiddenchart.update_every * 1000)) {
168                 if(typeof doNext == "function") doNext();
169                 return;
170         }
171
172         if(mainchart.dashboard && mainchart.hiddenchart.refreshCount > 50) {
173                 mainchart.dashboard.clear();
174                 mainchart.control_wrapper.clear();
175                 mainchart.hidden_wrapper.clear();
176
177                 mainchart.dashboard = null;
178                 mainchart.control_wrapper = null;
179                 mainchart.hidden_wrapper = null;
180                 mainchart.hiddenchart.last_updated = 0;
181         }
182
183         if(!mainchart.dashboard) {
184                 var controlopts = $.extend(true, {}, mainchart.chartOptions, {
185                         lineWidth: 1,
186                         height: mainchart.hiddenchart.chartOptions.height,
187                         chartArea: {'width': '98%'},
188                         hAxis: {'baselineColor': 'none', viewWindowMode: 'maximized'},
189                         vAxis: {'title': null},
190                 });
191
192                 mainchart.dashboard = new google.visualization.Dashboard(document.getElementById('maingraph_dashboard'));
193                 mainchart.control_wrapper = new google.visualization.ControlWrapper({
194                         controlType: 'ChartRangeFilter',
195                         containerId: 'maingraph_control',
196                         options: {
197                                 filterColumnIndex: 0,
198                                 ui: {
199                                         chartType: mainchart.chartType,
200                                         chartOptions: controlopts,
201                                         minRangeSize: (mainchart.max_time_to_show * 1000) / MAINCHART_POINTS_DIVISOR,
202                                 }
203                         },
204                 });
205                 mainchart.hidden_wrapper = new google.visualization.ChartWrapper({
206                         chartType: mainchart.chartType,
207                         containerId: 'maingraph_hidden',
208                         options: {
209                                 isStacked: mainchart.chartOptions.isStacked,
210                                 width: mainchart.hiddenchart.chartOptions.width,
211                                 height: mainchart.hiddenchart.chartOptions.height,
212                                 //chartArea: {'height': '80%', 'width': '100%'},
213                                 //hAxis: {'slantedText': false},
214                                 //legend: {'position': 'none'}
215                         },
216                 });
217
218                 mainchart.hiddenchart.refreshCount = 0;
219         }
220
221         // load the data for the control and the hidden wrappers
222         // calculate the group and points to show for the control chart
223         calculateChartPointsToShow(mainchart.hiddenchart, MAINCHART_CONTROL_DIVISOR, 0, -1);
224
225         $.ajax({
226                 url: generateChartURL(mainchart.hiddenchart),
227                 dataType:"json",
228                 cache: false
229         })
230         .done(function(jsondata) {
231                 if(!jsondata || jsondata.length == 0) return;
232
233                 mainchart.control_data = new google.visualization.DataTable(jsondata);
234
235                 if(mainchart.hiddenchart.last_updated == 0) {
236                         google.visualization.events.addListener(mainchart.control_wrapper, 'ready', mainchartControlReadyEvent);
237                         mainchart.dashboard.bind(mainchart.control_wrapper, mainchart.hidden_wrapper);
238                 }
239                 if(refresh_mode != REFRESH_PAUSED) {
240                         // console.log('mainchart.points_to_show: ' + mainchart.points_to_show + ', mainchart.group: ' + mainchart.group + ', mainchart.update_every: ' + mainchart.update_every);
241
242                         var start = now - (mainchart.points_to_show * mainchart.group * mainchart.update_every * 1000);
243                         var end = now;
244                         var min = MAINCHART_MIN_TIME_TO_SHOW * 1000;
245                         if(end - start < min) start = end - min;
246
247                         mainchart.control_wrapper.setState({range: {
248                                 start: new Date(start),
249                                 end: new Date(end)
250                         },
251                         ui: {
252                                 minRangeSize: min
253                         }});
254                 }
255
256                 mainchart.dashboard.draw(mainchart.control_data);
257                 mainchart.hiddenchart.last_updated = new Date().getTime();
258                 mainchart.hiddenchart.refreshCount++;
259         })
260         .always(function() {
261                 if(typeof doNext == "function") doNext();
262         });
263 }
264
265 function mainchartControlReadyEvent() {
266         google.visualization.events.addListener(mainchart.control_wrapper, 'statechange', mainchartControlStateHandler);
267         //mylog(mainchart);
268 }
269
270 function mainchartControlStateHandler() {
271         // setMainChartPlay('pause');
272
273         var state = mainchart.control_wrapper.getState();
274         mainchart.after = Math.round(state.range.start.getTime() / 1000);
275         mainchart.before = Math.round(state.range.end.getTime() / 1000);
276
277         calculateChartPointsToShow(mainchart, mainchart.chartOptions.isStacked?MAINCHART_STACKED_POINTS_DIVISOR:MAINCHART_POINTS_DIVISOR, 0, 0);
278         //mylog('group = ' + mainchart.group + ', points_to_show = ' + mainchart.points_to_show + ', dt = ' + (mainchart.before - mainchart.after));
279
280         $('#group' + mainchart.group).trigger('click');
281         mainchart.last_updated = 0;
282
283         if(refresh_mode != REFRESH_PAUSED) pauseGraphs();
284 }
285
286 function initMainChartIndex(i) {
287         if(mode == MODE_GROUP_THUMBS) 
288                 initMainChart(group_charts[i]);
289
290         else if(mode == MODE_THUMBS)
291                 initMainChart(mycharts[i]);
292
293         else
294                 initMainChart(mycharts[i]);
295 }
296
297 function initMainChartIndexOfMyCharts(i) {
298         initMainChart(mycharts[i]);
299 }
300
301 var last_main_chart_max='normal';
302 function setMainChartMax(m) {
303         if(!mainchart) return;
304
305         if(m == 'toggle') {
306                 if(last_main_chart_max == 'maximized') m = 'normal';
307                 else m = 'maximized';
308         }
309
310         if(m == "maximized") {
311                 mainchart.chartOptions.theme = 'maximized';
312                 //mainchart.chartOptions.axisTitlesPosition = 'in';
313                 //mainchart.chartOptions.legend = {position: 'none'};
314                 mainchart.chartOptions.hAxis.title = null;
315                 mainchart.chartOptions.hAxis.viewWindowMode = 'maximized';
316                 mainchart.chartOptions.vAxis.viewWindowMode = 'maximized';
317                 mainchart.chartOptions.chartArea = {'width': '98%', 'height': '100%'};
318         }
319         else {
320                 mainchart.chartOptions.hAxis.title = null;
321                 mainchart.chartOptions.theme = null;
322                 mainchart.chartOptions.hAxis.viewWindowMode = null;
323                 mainchart.chartOptions.vAxis.viewWindowMode = null;
324                 mainchart.chartOptions.chartArea = {'width': '80%', 'height': '90%'};
325         }
326         $('.mainchart_max_button').button(m);
327         last_main_chart_max = m;
328         mainchart.last_updated = 0;
329 }
330
331 function setMainChartGroup(g, norefresh) {
332         if(!mainchart) return;
333
334         mainchart.group = g;
335
336         if(!mainchart.before && !mainchart.after)
337                 calculateChartPointsToShow(mainchart, mainchart.chartOptions.isStacked?MAINCHART_STACKED_POINTS_DIVISOR:MAINCHART_POINTS_DIVISOR, mainchart.max_time_to_show, mainchart.group);
338         else
339                 calculateChartPointsToShow(mainchart, mainchart.chartOptions.isStacked?MAINCHART_STACKED_POINTS_DIVISOR:MAINCHART_POINTS_DIVISOR, 0, mainchart.group);
340
341         if(!norefresh) {
342                 mainchart.last_updated = 0;
343         }
344 }
345
346 var last_main_chart_avg = null;
347 function setMainChartGroupMethod(g, norefresh) {
348         if(!mainchart) return;
349
350         if(g == 'toggle') {
351                 if(last_main_chart_avg == 'max') g = 'average';
352                 else g = 'max';
353         }
354
355         mainchart.group_method = g;
356
357         $('.mainchart_avg_button').button(g);
358
359         if(!norefresh) {
360                 mainchart.last_updated = 0;
361         }
362
363         last_main_chart_avg = g;
364 }
365
366 function setMainChartPlay(p) {
367         if(!mainchart) return;
368
369         if(p == 'toggle') {
370                 if(refresh_mode != REFRESH_ALWAYS) p = 'play';
371                 else p = 'pause';
372         }
373
374         if(p == 'play') {
375                 //mainchart.chartOptions.explorer = null;
376                 mainchart.after = 0;
377                 mainchart.before = 0;
378                 calculateChartPointsToShow(mainchart, mainchart.chartOptions.isStacked?MAINCHART_STACKED_POINTS_DIVISOR:MAINCHART_POINTS_DIVISOR, mainchart.max_time_to_show, 0);
379                 $('#group' + mainchart.group).trigger('click');
380                 mainchart.last_updated = 0;
381                 mainchart.hiddenchart.last_updated = 0;
382                 playGraphs();
383         }
384         else {
385                 //mainchart.chartOptions.explorer = {
386                 //      'axis': 'horizontal',
387                 //      'maxZoomOut': 1,
388                 //};
389                 //mainchart.last_updated = 0;
390                 
391                 //if(!renderChart(mainchart, pauseGraphs))
392                 pauseGraphs();
393         }
394 }
395
396
397 // ------------------------------------------------------------------------
398 // Chart resizing
399
400 function screenWidth() {
401         return (($(window).width() * 0.95) - 50);
402 }
403
404 // calculate the proper width for the thumb charts
405 function thumbWidth() {
406         var cwidth = screenWidth();
407         var items = Math.round(cwidth / TARGET_THUMB_GRAPH_WIDTH);
408         if(items < 1) items = 1;
409
410         if(items > 1 && (cwidth / items) < MINIMUM_THUMB_GRAPH_WIDTH) items--;
411
412         return Math.round(cwidth / items) - 1;
413 }
414
415 function groupChartSizes() {
416         var s = { width: screenWidth() / 2, height: ($(window).height() - 130) / 3 - 10};
417         if(s.width < MINIMUM_THUMB_GRAPH_WIDTH * 1.5) s.width = screenWidth();
418
419         var count = 0;
420         if(group_charts) $.each(group_charts, function(i, c) {
421                 if(c.enabled) count++;
422         });
423
424         if(count == 0) {
425                 s.width = TARGET_THUMB_GRAPH_WIDTH;
426                 s.height = TARGET_THUMB_GRAPH_HEIGHT;
427         }
428         if(count < 4) {
429                 s.width = screenWidth();
430                 s.height = ($(window).height() - 130) / count - 10;
431         }
432         else if(count == 4) {
433                 s.height = ($(window).height() - 130) / 2 - 10;
434         }
435
436         if(s.height < TARGET_THUMB_GRAPH_HEIGHT * 1.5)
437                 s.height = TARGET_THUMB_GRAPH_HEIGHT * 1.5;
438
439         return s;
440 }
441
442 // resize all charts
443 // if the thumb charts need resize in their width, reset them
444 function resizeCharts() {
445         var width = screenWidth();
446
447         if(mainchart) {
448                 mainchart.chartOptions.width = width;
449                 mainchart.chartOptions.height = $(window).height() - 150 - MAINCHART_CONTROL_HEIGHT;
450                 mainchart.last_updated = 0;
451
452                 mainchart.hidden_wrapper.setOption('width', width);
453                 mainchart.control_wrapper.setOption('ui.chartOptions.width', width);
454                 mainchart.hiddenchart.chartOptions.width = width;
455                 mainchart.hiddenchart.last_updated = 0;
456         }
457
458         width = thumbWidth();
459         $.each(mycharts, function(i, c) {
460                 if(c.enabled && c.chartOptions.width != width) {
461                         cleanThisChart(c);
462                         c.chartOptions.width = width;
463                         calculateChartPointsToShow(c, c.chartOptions.isStacked?THUMBS_STACKED_POINTS_DIVISOR:THUMBS_POINTS_DIVISOR, THUMBS_MAX_TIME_TO_SHOW, -1);
464                         showChartIsLoading(c.div, c.name, c.chartOptions.width, c.chartOptions.height);
465                         c.last_updated = 0;
466                 }
467         });
468
469         if(group_charts) $.each(group_charts, function(i, c) {
470                 var sizes = groupChartSizes();
471
472                 if(c.enabled && (c.chartOptions.width != sizes.width || c.chartOptions.height != sizes.height)) {
473                         cleanThisChart(c);
474                         c.chartOptions.width = sizes.width;
475                         c.chartOptions.height = sizes.height;
476                         calculateChartPointsToShow(c, c.chartOptions.isStacked?GROUPS_STACKED_POINTS_DIVISOR:GROUPS_POINTS_DIVISOR, GROUPS_MAX_TIME_TO_SHOW, -1);
477                         showChartIsLoading(c.div, c.name, c.chartOptions.width, c.chartOptions.height);
478                         c.last_updated = 0;
479                 }
480         });
481 }
482
483 var resize_request = false;
484 window.onresize = function(event) {
485         resize_request = true;
486 };
487
488
489 // ------------------------------------------------------------------------
490 // Core of the thread refreshing the charts
491
492 var REFRESH_PAUSED = 0;
493 var REFRESH_ALWAYS = 1;
494
495 var refresh_mode = REFRESH_PAUSED;
496 var last_refresh = 0;
497 function playGraphs() {
498         if(refresh_mode == REFRESH_ALWAYS) return;
499
500         //mylog('PlayGraphs()');
501         refresh_mode = REFRESH_ALWAYS;
502         $('.mainchart_play_button').button('play');
503
504         // check if the thread died due to a javascript error
505         var now = new Date().getTime();
506         if((now - last_refresh) > 60000) {
507                 // it died or never started
508                 //mylog('It seems the refresh thread died. Restarting it.');
509                 chartsRefresh();
510         }
511 }
512
513 function pauseGraphs() {
514         if(refresh_mode == REFRESH_PAUSED) return;
515
516         //mylog('PauseGraphs()');
517         refresh_mode = REFRESH_PAUSED;
518         $('.mainchart_play_button').button('pause');
519 }
520
521 var interval = null;
522 function checkRefreshThread() {
523         if(interval == null) {
524                 interval = setInterval(checkRefreshThread, 2000);
525                 return;
526         }
527
528         var now = new Date().getTime();
529         if(now - last_refresh > 60000) {
530                 mylog('Refresh thread died. Restarting it.');
531                 chartsRefresh();
532         }
533 }
534
535 // refresh the proper chart
536 // this is an internal function.
537 // never call it directly, or new javascript threads will be spawn
538 var timeout = null;
539 function chartsRefresh() {
540         last_refresh = new Date().getTime();
541         
542         if(!page_is_visible) {
543                 timeout = setTimeout(triggerRefresh, CHARTS_CHECK_NO_FOCUS);
544                 return;
545         }
546
547         if(resize_request) {
548                 resizeCharts();
549                 resize_request = false;
550                 // refresh_mode = REFRESH_ALWAYS;
551         }
552
553         if(last_user_scroll) {
554                 var now = new Date().getTime();
555                 if((now - last_user_scroll) >= CHARTS_SCROLL_IDLE) {
556                         last_user_scroll = 0;
557                         mylog('Scrolling: resuming refresh...');
558                 }
559                 else {
560                         mylog('Scrolling: pausing refresh for ' + (CHARTS_SCROLL_IDLE - (now - last_user_scroll)) + ' ms...');
561                         timeout = setTimeout(triggerRefresh, CHARTS_SCROLL_IDLE - (now - last_user_scroll));
562                         return;
563                 }
564         }
565
566         if(refresh_mode == REFRESH_PAUSED) {
567                 if(mode == MODE_MAIN && mainchart.last_updated == 0) {
568                         mainChartRefresh();
569                         return;
570                 }
571         }
572
573              if(mode == MODE_THUMBS)            timeout = setTimeout(thumbChartsRefresh, CHARTS_REFRESH_LOOP);
574         else if(mode == MODE_GROUP_THUMBS)  timeout = setTimeout(groupChartsRefresh, CHARTS_REFRESH_LOOP);
575         else if(mode == MODE_MAIN)              timeout = setTimeout(mainChartRefresh, CHARTS_REFRESH_LOOP);
576         else                                    timeout = setTimeout(triggerRefresh, CHARTS_REFRESH_IDLE);
577 }
578
579 // callback for refreshing the charts later
580 // this is an internal function.
581 // never call it directly, or new javascript threads will be spawn
582 function triggerRefresh() {
583         //mylog('triggerRefresh()');
584
585         // cleanup has to take place when the charts are not refreshed
586         // since the refreshing thread is in this function, it means
587         // nothing is being refreshed.
588         cleanupCharts();
589
590              if(mode == MODE_THUMBS)            timeout = setTimeout(chartsRefresh, CHARTS_REFRESH_IDLE);
591         else if(mode == MODE_GROUP_THUMBS)      timeout = setTimeout(chartsRefresh, CHARTS_REFRESH_IDLE);
592         else if(mode == MODE_MAIN)              timeout = setTimeout(chartsRefresh, CHARTS_REFRESH_IDLE);
593         else                                    timeout = setTimeout(triggerRefresh, CHARTS_REFRESH_IDLE);
594 }
595
596 // refresh the main chart
597 // make sure we don't loose the refreshing thread
598 function mainChartRefresh() {
599         //mylog('mainChartRefresh()');
600
601         if(mode != MODE_MAIN || !mainchart) {
602                 triggerRefresh();
603                 return;
604         }
605
606         if(refresh_mode == REFRESH_PAUSED && mainchart.last_updated != 0) {
607                 hiddenChartRefresh();
608                 return;
609         }
610
611         if(!renderChart(mainchart, hiddenChartRefresh))
612                 hiddenChartRefresh();
613 }
614
615 function hiddenChartRefresh() {
616         refreshHiddenChart(triggerRefresh);
617 }
618
619 function roundRobinRefresh(charts, startat) {
620         var refreshed = false;
621
622         // find a chart to refresh
623         var all = charts.length;
624         var cur = startat + 1;
625         var count = 0;
626
627         for(count = 0; count < all ; count++, cur++) {
628                 if(cur >= all) cur = 0;
629
630                 if(charts[cur].enabled) {
631                         refreshed = renderChart(charts[cur], chartsRefresh);
632                         if(refreshed) {
633                                 mylog('Refreshed: ' + charts[cur].name);
634                                 break;
635                         }
636                 }
637         }
638
639         if(!refreshed) triggerRefresh();
640         return cur;
641 }
642
643 // refresh the thumb charts
644 // make sure we don't loose the refreshing thread
645 var last_thumb_updated = 0;
646 function thumbChartsRefresh() {
647         //mylog('thumbChartsRefresh()');
648
649         if(mycharts.length == 0 || mode != MODE_THUMBS) {
650                 triggerRefresh();
651                 return;
652         }
653
654         last_thumb_updated = roundRobinRefresh(mycharts, last_thumb_updated);
655 }
656
657 // refresh the group charts
658 // make sure we don't loose the refreshing thread
659 var last_group_updated = 0;
660 function groupChartsRefresh() {
661         //mylog('groupChartsRefresh()');
662
663         if(!group_charts || group_charts.length == 0 || mode != MODE_GROUP_THUMBS) {
664                 //mylog('cannot refresh charts');
665                 triggerRefresh();
666                 return;
667         }
668
669         last_group_updated = roundRobinRefresh(group_charts, last_group_updated);
670 }
671
672
673 // ------------------------------------------------------------------------
674 // switch the screen between views
675 // these should be called only from initXXXX()
676
677 function disableChart(i) {
678         //mylog('disableChart(' + i + ')');
679
680         var chart = null;
681
682         var count = 0;
683         if(mode == MODE_GROUP_THUMBS && group_charts) {
684                 $.each(group_charts, function(i, c) {
685                         if(c.enabled) count++;
686                 });
687
688                 if(i < group_charts.length) chart = group_charts[i];
689         }
690         else if(mode == MODE_THUMBS) {
691                 $.each(mycharts, function(i, c) {
692                         if(c.enabled) count++;
693                 });
694
695                 if(i < mycharts.length) chart = mycharts[i];
696         }
697
698         if(!chart) return;
699
700         if(count <= 1) {
701                 alert('Cannot close the last chart shown.');
702                 return;
703         }
704
705         if(chart) {
706                 //mylog("disabling chart " + chart.name);
707                 chart.disablethisplease = true;
708         }
709 }
710
711 function cleanThisChart(chart, emptydivs) {
712         //mylog('cleanThisChart(' + chart.name + ', ' + emptydivs +')');
713
714         if(chart.dashboard) {
715                 chart.dashboard.clear();
716                 chart.dashboard = null;
717
718                 if(chart.control_wrapper) {
719                         chart.control_wrapper.clear();
720                         chart.control_wrapper = null;
721                 }
722
723                 if(chart.hidden_wrapper) {
724                         chart.hidden_wrapper.clear();
725                         chart.hidden_wrapper = null;
726                 }
727
728                 chart.control_data = null;
729         }
730
731         if(chart.chart) chart.chart.clearChart();
732         chart.chart = null;
733
734         if(emptydivs) {
735                 var div = document.getElementById(chart.div);
736                 if(div) {
737                         div.style.display = 'none';
738                         div.innerHTML = "";
739                 }
740
741                 div = document.getElementById(chart.div + "_parent");
742                 if(div) {
743                         div.style.display = 'none';
744                         div.innerHTML = "";
745                 }
746         }
747
748         //mylog("chart " + chart.name + " cleaned with option " + emptydivs);
749 }
750
751 // cleanup the previously shown charts
752 function cleanupCharts() {
753         //mylog('cleanupCharts()');
754
755         if(mode != MODE_MAIN && mainchart) {
756                 if(mainchart.chart) cleanThisChart(mainchart);
757                 mainchart = null;
758         }
759
760         if(mode != MODE_GROUP_THUMBS && group_charts) {
761                 clearGroupGraphs();
762         }
763
764         // cleanup the disabled charts
765         $.each(mycharts, function(i, c) {
766                 if(c.disablethisplease && c.enabled) {
767                         cleanThisChart(c, 'emptydivs');
768                         c.disablethisplease = false;
769                         c.enabled = false;
770                         resize_request = true;
771                         //mylog("disabled chart " + c.name + " removed");
772                 }
773         });
774
775         if(group_charts) $.each(group_charts, function(i, c) {
776                 if(c.disablethisplease && c.enabled) {
777                         cleanThisChart(c, 'emptydivs');
778                         c.disablethisplease = false;
779                         c.enabled = false;
780                         resize_request = true;
781                         //mylog("disabled chart " + c.name + " removed");
782                 }
783         });
784
785         // we never cleanup the thumb charts
786 }
787
788 function updateUI() {
789         $('[data-toggle="tooltip"]').tooltip({'placement': 'top', 'container': 'body', 'html': true});
790
791         $('[data-spy="scroll"]').each(function () {
792                 var $spy = $(this).scrollspy('refresh')
793         })
794 }
795
796 var thumbsScrollPosition = null;
797 function switchToMainGraph() {
798         //mylog('switchToMainGraph()');
799
800         if(!mainchart) return;
801
802         if(!group_charts) thumbsScrollPosition = window.pageYOffset;
803
804         document.getElementById('maingraph_container').style.display = 'block';
805         document.getElementById('thumbgraphs_container').style.display = 'none';
806         document.getElementById('groupgraphs_container').style.display = 'none';
807         document.getElementById('splash_container').style.display = 'none';
808
809         document.getElementById("main_menu_div").innerHTML = "<ul class=\"nav navbar-nav\"><li><a href=\"javascript:switchToThumbGraphs();\"><span class=\"glyphicon glyphicon-circle-arrow-left\"></span> Back to Dashboard</a></li><li class=\"active\"><a href=\"#\">" + mainchart.name + "</a></li>" + familiesmainmenu + chartsmainmenu + "</ul>";
810
811         window.scrollTo(0, 0);
812
813         mode = MODE_MAIN;
814         playGraphs();
815         updateUI();
816 }
817
818 function switchToThumbGraphs() {
819         //mylog('switchToThumbGraphs()');
820
821         document.getElementById('maingraph_container').style.display = 'none';
822         document.getElementById('thumbgraphs_container').style.display = 'block';
823         document.getElementById('groupgraphs_container').style.display = 'none';
824         document.getElementById('splash_container').style.display = 'none';
825
826         document.getElementById("main_menu_div").innerHTML = mainmenu;
827
828         if(thumbsScrollPosition) window.scrollTo(0, thumbsScrollPosition);
829
830         // switch mode
831         mode = MODE_THUMBS;
832         playGraphs();
833         updateUI();
834 }
835
836 function switchToGroupGraphs() {
837         //mylog('switchToGroupGraphs()');
838
839         if(!group_charts) return;
840
841         if(!mainchart) thumbsScrollPosition = window.pageYOffset;
842
843         document.getElementById('maingraph_container').style.display = 'none';
844         document.getElementById('thumbgraphs_container').style.display = 'none';
845         document.getElementById('groupgraphs_container').style.display = 'block';
846         document.getElementById('splash_container').style.display = 'none';
847
848         document.getElementById("main_menu_div").innerHTML = "<ul class=\"nav navbar-nav\"><li><a href=\"javascript:switchToThumbGraphs();\"><span class=\"glyphicon glyphicon-circle-arrow-left\"></span> Back to Dashboard</a></li><li class=\"active\"><a href=\"#\">" + group_charts[0].family + "</a></li>" + familiesmainmenu + chartsmainmenu + "</ul>";
849
850         window.scrollTo(0, 0);
851
852         mode = MODE_GROUP_THUMBS;
853         playGraphs();
854         updateUI();
855 }
856
857
858 // ------------------------------------------------------------------------
859 // Group Charts
860
861 var group_charts = null;
862 function initGroupGraphs(group) {
863         var count = 0;
864         
865         if(group_charts) clearGroupGraphs();
866         group_charts = new Array();
867
868         var groupbody = "";
869         $.each(mycharts, function(i, c) {
870                 if(c.family == group) {
871                         group_charts[count] = [];
872                         group_charts[count] = $.extend(true, {}, c);
873                         group_charts[count].div += "_group";
874                         group_charts[count].enabled = true;
875                         group_charts[count].chart = null;
876                         group_charts[count].last_updated = 0;
877                         count++;
878                 }
879         });
880         group_charts.sort(chartssort);
881
882         var sizes = groupChartSizes();
883
884         var groupbody = "";
885         $.each(group_charts, function(i, c) {
886                 c.chartOptions.width = sizes.width;
887                 c.chartOptions.height = sizes.height;
888
889                 calculateChartPointsToShow(c, c.chartOptions.isStacked?GROUPS_STACKED_POINTS_DIVISOR:GROUPS_POINTS_DIVISOR, GROUPS_MAX_TIME_TO_SHOW, -1);
890
891                 groupbody += "<div class=\"thumbgraph\" id=\"" + c.div + "_parent\"><table><tr><td><div class=\"thumbgraph\" id=\"" + c.div + "\">" + chartIsLoadingHTML(c.name, c.chartOptions.width, c.chartOptions.height) + "</div></td></tr><tr><td align=\"center\">" + thumbChartActions(i, c, 'nogroup') + "</td></tr><tr><td height='15'></td></tr></table></div>";
892         });
893         groupbody += "";
894
895         document.getElementById("groupgraphs").innerHTML = groupbody;
896         switchToGroupGraphs();
897 }
898
899 function clearGroupGraphs() {
900         if(group_charts && group_charts.length) {
901                 $.each(group_charts, function(i, c) {
902                         cleanThisChart(c, 'emptydivs');
903                 });
904
905                 group_charts = null;
906         }
907
908         document.getElementById("groupgraphs").innerHTML = "";
909 }
910
911
912 // ------------------------------------------------------------------------
913 // Global entry point
914 // initialize the thumb charts
915
916 var last_user_scroll = 0;
917
918 // load the charts from the server
919 // generate html for the thumbgraphs to support them
920 function initCharts() {
921         var width = thumbWidth();
922         var height = TARGET_THUMB_GRAPH_HEIGHT;
923
924         window.onscroll = function (e) {
925                 last_user_scroll = new Date().getTime();
926                 mylog('Scrolling: detected');
927         }
928
929         loadCharts(null, function(all) {
930                 mycharts = all.charts;
931
932                 if(mycharts == null || mycharts.length == 0) {
933                         alert("Cannot load data from server.");
934                         return;
935                 }
936
937                 var thumbsContainer = document.getElementById("thumbgraphs");
938                 if(!thumbsContainer) {
939                         alert("Cannot find the thumbsContainer");
940                         return;
941                 }
942
943                 mycharts.sort(chartssort);
944
945                 document.getElementById('hostname_id').innerHTML = all.hostname;
946                 document.title = all.hostname;
947
948                 // create an array for grouping all same-type graphs together
949                 var dimensions = 0;
950                 var categories = new Array();
951                 var families = new Array();
952                 var chartslist = new Array();
953                 $.each(mycharts, function(i, c) {
954                         var j;
955
956                         chartslist.push({name: c.name, type: c.type, id: i});
957
958                         dimensions += c.dimensions.length;
959                         c.chartOptions.width = width;
960                         c.chartOptions.height = height;
961
962                         // calculate how many point to show for each chart
963                         //c.points_to_show = Math.round(c.entries / c.group) - 1;
964                         // show max 10 mins of data
965                         //if(c.points_to_show * c.group > THUMBS_MAX_TIME_TO_SHOW) c.points_to_show = THUMBS_MAX_TIME_TO_SHOW / c.group;
966                         calculateChartPointsToShow(c, c.chartOptions.isStacked?THUMBS_STACKED_POINTS_DIVISOR:THUMBS_POINTS_DIVISOR, THUMBS_MAX_TIME_TO_SHOW, -1);
967
968                         if(c.enabled) {
969                                 var h = "<div class=\"thumbgraph\" id=\"" + c.div + "_parent\"><table><tr><td><div class=\"thumbgraph\" id=\"" + c.div + "\">" + chartIsLoadingHTML(c.name, c.chartOptions.width, c.chartOptions.height) + "</div></td></tr><tr><td align=\"center\">"
970                                 + thumbChartActions(i, c)
971                                 +       "</td></tr><tr><td height='15'></td></tr></table></div>";
972
973                                 // find the categories object for this type
974                                 for(j = 0; j < categories.length ;j++) {
975                                         if(categories[j].name == c.type) {
976                                                 categories[j].html += h;
977                                                 categories[j].count++;
978                                                 break;
979                                         }
980                                 }
981
982                                 if(j == categories.length)
983                                         categories.push({name: c.type, title: c.category, description: '', priority: c.categoryPriority, count: 1, glyphicon: c.glyphicon, html: h});
984                         }
985
986                         // find the families object for this type
987                         for(j = 0; j < families.length ;j++) {
988                                 if(families[j].name == c.family) {
989                                         families[j].count++;
990                                         break;
991                                 }
992                         }
993
994                         if(j == families.length)
995                                 families.push({name: c.family, count: 1});
996                 });
997
998                 document.getElementById('server_summary_id').innerHTML = "<small>NetData server at <b>" + all.hostname + "</b> is maintaining <b>" + mycharts.length + "</b> charts, having <b>" + dimensions + "</b> dimensions (by default with <b>" + all.history + "</b> entries each), which are updated every <b>" + all.update_every + "s</b>, using a total of <b>" + (Math.round(all.memory * 10 / 1024 / 1024) / 10) + " MB</b> for the round robin database.</small>";
999
1000                 $.each(categories, function(i, a) {
1001                         a.html = "<tr><td id=\"" + a.name + "\"><ol class=\"breadcrumb graphs\"><li class=\"active\"><span class=\"glyphicon " + a.glyphicon + "\"></span> &nbsp; <a id=\"" + a.name + "\" href=\"#" + a.name + "\"><b>" + a.title + "</b> " + a.description + " </a></li></ol></td></tr><tr><td><div class=\"thumbgraphs\">" + a.html + "</td></tr>";
1002                 });
1003
1004                 function categoriessort(a, b) {
1005                         if(a.priority < b.priority) return -1;
1006                         return 1;
1007                 }
1008                 categories.sort(categoriessort);
1009                 
1010                 function familiessort(a, b) {
1011                         if(a.name < b.name) return -1;
1012                         return 1;
1013                 }
1014                 families.sort(familiessort);
1015
1016                 function chartslistsort(a, b) {
1017                         if(a.name < b.name) return -1;
1018                         return 1;
1019                 }
1020                 chartslist.sort(chartslistsort);
1021
1022                 // combine all the htmls into one
1023                 var allcategories = "<table width=\"100%\">";
1024                 mainmenu = '<ul class="nav navbar-nav">';
1025
1026                 categoriesmainmenu = '<li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown"><span class=\"glyphicon glyphicon-fire\"></span> Dashboard Sections <b class="caret"></b></a><ul class="dropdown-menu">';
1027                 $.each(categories, function(i, a) {
1028                         allcategories += a.html;
1029                         categoriesmainmenu += "<li><a href=\"#" + a.name + "\">" + a.title + "</a></li>";
1030                 });
1031                 categoriesmainmenu += "</ul></li>";
1032                 allcategories += "</table>";
1033
1034                 familiesmainmenu = '<li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown"><span class=\"glyphicon glyphicon-th-large\"></span> Chart Families <b class="caret"></b></a><ul class="dropdown-menu">';
1035                 $.each(families, function(i, a) {
1036                         familiesmainmenu += "<li><a href=\"javascript:initGroupGraphs('" + a.name + "');\">" + a.name + " <span class=\"badge pull-right\">" + a.count + "</span></a></li>";
1037                 });
1038                 familiesmainmenu += "</ul></li>";
1039
1040                 chartsmainmenu = '<li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown"><span class=\"glyphicon glyphicon-resize-full\"></span> All Charts <b class="caret"></b></a><ul class="dropdown-menu">';
1041                 $.each(chartslist, function(i, a) {
1042                         chartsmainmenu += "<li><a href=\"javascript:initMainChartIndexOfMyCharts('" + a.id + "');\">" + a.name + "</a></li>";
1043                 });
1044                 chartsmainmenu += "</ul></li>";
1045
1046                 mainmenu += categoriesmainmenu;
1047                 mainmenu += familiesmainmenu;
1048                 mainmenu += chartsmainmenu;
1049                 mainmenu += '<li role="presentation" class="disabled" style="display: none;"><a href="#" id="logline"></a></li></ul>';
1050
1051                 thumbsContainer.innerHTML = allcategories;
1052                 switchToThumbGraphs();
1053                 checkRefreshThread();
1054         });
1055 }
1056
1057 $(window).blur(function() {
1058         page_is_visible = 0;
1059         mylog('Lost Focus!');
1060 });
1061
1062 $(window).focus(function() {
1063         page_is_visible = 1;
1064         mylog('Focus restored!');
1065 });
1066
1067 // Set a callback to run when the Google Visualization API is loaded.
1068 google.setOnLoadCallback(initCharts);