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