fnordmetric 0.5.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. data/.travis.yml +1 -0
  2. data/VERSION +1 -1
  3. data/doc/preview1.png +0 -0
  4. data/doc/preview2.png +0 -0
  5. data/doc/ulm_stats.rb +622 -0
  6. data/doc/version +1 -0
  7. data/fnordmetric.gemspec +16 -38
  8. data/haml/app.haml +12 -5
  9. data/lib/fnordmetric.rb +3 -0
  10. data/lib/fnordmetric/app.rb +19 -10
  11. data/lib/fnordmetric/bars_widget.rb +26 -0
  12. data/lib/fnordmetric/context.rb +3 -3
  13. data/lib/fnordmetric/gauge.rb +20 -0
  14. data/lib/fnordmetric/gauge_calculations.rb +28 -4
  15. data/lib/fnordmetric/gauge_modifiers.rb +39 -6
  16. data/lib/fnordmetric/logger.rb +19 -0
  17. data/lib/fnordmetric/numbers_widget.rb +5 -15
  18. data/lib/fnordmetric/pie_widget.rb +23 -0
  19. data/lib/fnordmetric/standalone.rb +1 -1
  20. data/lib/fnordmetric/timeline_widget.rb +16 -23
  21. data/lib/fnordmetric/toplist_widget.rb +25 -0
  22. data/lib/fnordmetric/widget.rb +3 -3
  23. data/pub/{fnordmetric/fnordmetric.css → fnordmetric.css} +46 -36
  24. data/pub/fnordmetric.js +1069 -0
  25. data/pub/loader.gif +0 -0
  26. data/pub/{highcharts → vendor}/highcharts.js +0 -0
  27. data/pub/{jquery-1.6.1.min.js → vendor/jquery-1.6.1.min.js} +0 -0
  28. data/readme.rdoc +228 -311
  29. data/spec/app_spec.rb +63 -3
  30. data/spec/gauge_modifiers_spec.rb +157 -2
  31. data/spec/gauge_spec.rb +143 -12
  32. data/spec/widget_spec.rb +18 -18
  33. metadata +33 -58
  34. data/.document +0 -5
  35. data/_spec/app_spec.rb +0 -178
  36. data/_spec/cache_spec.rb +0 -53
  37. data/_spec/combine_metric_spec.rb +0 -19
  38. data/_spec/core_spec.rb +0 -50
  39. data/_spec/count_metric_spec.rb +0 -32
  40. data/_spec/dashboard_spec.rb +0 -67
  41. data/_spec/event_spec.rb +0 -46
  42. data/_spec/metric_spec.rb +0 -118
  43. data/_spec/report_spec.rb +0 -87
  44. data/_spec/sum_metric_spec.rb +0 -33
  45. data/_spec/widget_spec.rb +0 -107
  46. data/doc/example_server.rb +0 -56
  47. data/doc/import_dump.rb +0 -26
  48. data/pub/fnordmetric/fnordmetric.js +0 -543
  49. data/pub/fnordmetric/widget_numbers.js +0 -71
  50. data/pub/fnordmetric/widget_timeline.css +0 -0
  51. data/pub/fnordmetric/widget_timeline.js +0 -110
  52. data/pub/highcharts/adapters/mootools-adapter.js +0 -12
  53. data/pub/highcharts/adapters/mootools-adapter.src.js +0 -243
  54. data/pub/highcharts/adapters/prototype-adapter.js +0 -14
  55. data/pub/highcharts/adapters/prototype-adapter.src.js +0 -284
  56. data/pub/highcharts/highcharts.src.js +0 -11103
  57. data/pub/highcharts/modules/exporting.js +0 -22
  58. data/pub/highcharts/modules/exporting.src.js +0 -703
  59. data/pub/highcharts/themes/dark-blue.js +0 -268
  60. data/pub/highcharts/themes/dark-green.js +0 -268
  61. data/pub/highcharts/themes/gray.js +0 -262
  62. data/pub/highcharts/themes/grid.js +0 -97
  63. data/pub/raphael-min.js +0 -8
  64. data/pub/raphael-utils.js +0 -221
  65. data/ulm_stats.rb +0 -198
@@ -0,0 +1,23 @@
1
+ class FnordMetric::PieWidget < FnordMetric::Widget
2
+
3
+ def data
4
+ super.merge(
5
+ :gauges => gauges.map(&:name),
6
+ :gauge_titles => gauge_titles,
7
+ :autoupdate => (@opts[:autoupdate] || 60)
8
+ )
9
+ end
10
+
11
+ def gauge_titles
12
+ {}.tap do |_hash|
13
+ gauges.each do |gauge|
14
+ _hash.merge!(gauge.name => gauge.title)
15
+ end
16
+ end
17
+ end
18
+
19
+ def has_tick?
20
+ false
21
+ end
22
+
23
+ end
@@ -10,7 +10,7 @@ task :log do
10
10
  end
11
11
 
12
12
  task :import do
13
- puts 'not yet implemented :('
13
+ FnordMetric::Logger.import(dump_file_path)
14
14
  end
15
15
 
16
16
  task :help do
@@ -1,35 +1,28 @@
1
1
  class FnordMetric::TimelineWidget < FnordMetric::Widget
2
2
 
3
- def data_labels
4
- ticks.map do |t|
5
- Time.at(t).strftime('%d.%m.%y %H:%M')
6
- end
7
- end
8
-
9
- def data_series
10
- gauges.map do |gauge|
11
- {
12
- :color => next_series_colour,
13
- :data => ticks.map{ |t| gauge.value_at(t)||0 }
14
- }
15
- end
16
- end
17
-
18
- def next_series_colour
19
- @series_colors.pop.tap do |color|
20
- @series_colors.unshift(color)
21
- end
22
- end
23
-
24
3
  def data
25
4
  @series_colors = ["#FACE4F", "#42436B", "#CD645A", "#2F635E"]
26
5
 
27
6
  super.merge(
28
- :labels => data_labels,
29
- :series => data_series
7
+ :gauges => gauges.map(&:name),
8
+ :gauge_titles => gauge_titles,
9
+ :start_timestamp => ticks.first,
10
+ :end_timestamp => ticks.last,
11
+ :autoupdate => (@opts[:autoupdate] || 60),
12
+ :include_current => !!@opts[:include_current],
13
+ :plot_style => (@opts[:plot_style] || 'line'),
14
+ :tick => tick
30
15
  )
31
16
  end
32
17
 
18
+ def gauge_titles
19
+ {}.tap do |_hash|
20
+ gauges.each do |gauge|
21
+ _hash.merge!(gauge.name => gauge.title)
22
+ end
23
+ end
24
+ end
25
+
33
26
  def has_tick?
34
27
  true
35
28
  end
@@ -0,0 +1,25 @@
1
+ class FnordMetric::ToplistWidget < FnordMetric::Widget
2
+
3
+ def data
4
+ super.merge(
5
+ :gauges => data_gauges,
6
+ :autoupdate => (@opts[:autoupdate] || 0)
7
+ )
8
+ end
9
+
10
+ def data_gauges
11
+ Hash.new.tap do |hash|
12
+ gauges.each do |g|
13
+ hash[g.name] = {
14
+ :tick => g.tick,
15
+ :title => g.title
16
+ }
17
+ end
18
+ end
19
+ end
20
+
21
+ def has_tick?
22
+ false
23
+ end
24
+
25
+ end
@@ -29,7 +29,7 @@ class FnordMetric::Widget
29
29
 
30
30
  if (ticks = gauges.map{ |g| g.tick }).uniq.length == 1
31
31
  @tick = ticks.first
32
- else
32
+ elsif !!self.try(:has_tick?)
33
33
  error! "you can't add gauges with different ticks to the same widget"
34
34
  end
35
35
  end
@@ -52,8 +52,8 @@ class FnordMetric::Widget
52
52
  def default_range(now=Time.now)
53
53
  ensure_has_tick!
54
54
  te = gauges.first.tick_at(now.to_i)
55
- te += @tick if include_current?
56
- rs = @tick == 1.hour.to_i ? 24 : 30
55
+ te -= @tick unless include_current?
56
+ rs = (@opts[:ticks] || (@tick == 1.hour.to_i ? 24 : 30)).to_i
57
57
  (te-(@tick*rs)..te)
58
58
  end
59
59
 
@@ -24,24 +24,53 @@ body{ background:#3b3e45; color:#333; margin:0; padding:0; overflow-y:scroll; fo
24
24
  .widget{ min-height:100px; border-right:1px solid #ececec; float:left; }
25
25
  .widget.full_width{ border-right:none; }
26
26
  .widget .inner{ margin:20px; }
27
- .widget .headbar{ margin-bottom:30px; }
27
+ /*.widget .headbar{ margin-bottom:30px; }*/
28
28
 
29
- /*
30
- .headbar{ height:36px; background:#f2f2f2; border-bottom:1px solid #e2e2e2; }
31
- .headbar h2{ margin:8px; line-height:21px; float:left; height:20px; font-size:14px; }
32
- .headbar .datepicker{ background:#fff; border:1px solid #999; height:20px; padding:0 7px; float:right; margin:8px -1px; min-width:100px; font-size:11px; font-style:italic; }
33
- .headbar .button.mr{ margin-right:16px; }
29
+ .toplist_inner{ min-height:300px; }
30
+ .toplist_inner.loading{ opacity:0.5; background:url('/loader.gif') no-repeat center center; }
34
31
 
32
+ .toplist_item{ border-bottom:1px solid #dedede; height:42px; }
33
+ .toplist_item .title{ float:left; line-height:42px; margin-left:20px; font-size:13px; color:#333; }
34
+ .toplist_item .value{ float:right; line-height:42px; margin-right:20px; font-size:13px; font-weight:bold; color:#333; width:70px; color:#666; }
35
+ .toplist_item .percent{ float:right; line-height:42px; margin-right:20px; font-size:18px; font-weight:bold; color:#333; width:70px; }
36
+
37
+ .headbar {
38
+
39
+ height:36px;
40
+ background-color: #F4F4F4;
41
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#f4f4f4), to(#e9e9e9));
42
+ background-image: -webkit-linear-gradient(top, #f4f4f4, #e9e9e9);
43
+ background-image: -moz-linear-gradient(top, #f4f4f4, #e9e9e9);
44
+ background-image: -ms-linear-gradient(top, #f4f4f4, #e9e9e9);
45
+ background-image: -o-linear-gradient(top, #f4f4f4, #e9e9e9);
46
+ background-image: linear-gradient(top, #f4f4f4, #e9e9e9);
47
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#f4f4f4', EndColorStr='#e9e9e9');
48
+ padding: 0 15px;
49
+ border-bottom: 1px solid #C9C9C9;
50
+ border-top: 1px solid #d0d0d0;
51
+ font-size:13px;
52
+ line-height:29px;
53
+ text-shadow: 1px 0px 2px rgba(255, 255, 255, 1);
54
+ -moz-text-shadow: 1px 0px 2px rgba(255,255,255,1);
55
+ -webkit-text-shadow: 1px 0px 2px rgba(255,255,255,1);
56
+ overflow:hidden;
57
+ }
58
+
59
+ .headbar.small{ height:29px; }
60
+
61
+ .headbar h2{ line-height:37px; margin:0; float:left; font-size:14px; }
62
+ .headbar .datepicker{ background:#fff; border:1px solid #999; height:20px; padding:0 7px; float:right; margin:8px -1px; min-width:100px; font-size:11px; font-style:italic; line-height:21px; }
63
+ .headbar .button.mr{ margin-right:16px; }
35
64
 
36
65
  .headbar .button{
37
66
  margin:8px 0px; height:16px; float:right; display:block;
38
67
  margin-right:-1px;
39
- background:url('/fnordmetric/sprite.png') no-repeat 0 -49px #eee;
68
+ background:url('/sprite.png') no-repeat 0 -49px #eee;
40
69
  border:1px solid #999;
41
70
  border-bottom-color:#888;
42
71
  -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, .1);
43
72
  cursor:pointer;
44
-
73
+ border-radius:3px;
45
74
  font-size: 11px;
46
75
  font-weight:bold;
47
76
  line-height:16px;
@@ -53,36 +82,16 @@ body{ background:#3b3e45; color:#333; margin:0; padding:0; overflow-y:scroll; fo
53
82
  }
54
83
 
55
84
  .headbar .button:hover, .headbar.button.active{background:#ddd;border-bottom-color:#999;-webkit-box-shadow:0 1px 0 rgba(0, 0, 0, .05)}
56
- */
57
- .numbers_container, .number{ float:left; border-right:1px solid #ececec; }
58
- .number{ margin-left:12px; padding-right:20px; margin-right:10px; }
59
- .number .value{ color:#333; font-size:30px; display:block; margin-bottom:5px; }
60
- .number .desc{ color:#999; font-size:12px; }
61
- .number:last-child{ border-right:none; }
62
- .numbers_container{ padding-right:0px; width:33.2%; }
63
-
64
- .numbers_container .title{ padding:4px 10px 1px 10px; color:#333; font-size:13px; display:block; background:#f2f2f2; border-bottom:1px solid #e2e2e2; margin-bottom:15px; }
65
85
 
66
86
 
67
- .headbar {
68
- background-color: #F4F4F4;
69
- background-image: -webkit-gradient(linear, left top, left bottom, from(#f4f4f4), to(#e9e9e9));
70
- background-image: -webkit-linear-gradient(top, #f4f4f4, #e9e9e9);
71
- background-image: -moz-linear-gradient(top, #f4f4f4, #e9e9e9);
72
- background-image: -ms-linear-gradient(top, #f4f4f4, #e9e9e9);
73
- background-image: -o-linear-gradient(top, #f4f4f4, #e9e9e9);
74
- background-image: linear-gradient(top, #f4f4f4, #e9e9e9);
75
- filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#f4f4f4', EndColorStr='#e9e9e9');
76
- padding: 0 15px;
77
- border-bottom: 1px solid #C9C9C9;
78
- border-top: 1px solid #F9F9F9;
79
- height: 28px;
80
- font-size:13px;
81
- line-height:29px;
82
- text-shadow: 1px 0px 2px rgba(255, 255, 255, 1);
83
- -moz-text-shadow: 1px 0px 2px rgba(255,255,255,1);
84
- -webkit-text-shadow: 1px 0px 2px rgba(255,255,255,1);
85
- }
87
+ .numbers_container, .number{ float:left; width:60px; }
88
+ .number{ padding-right:20px; margin-right:6px; }
89
+ .number .value{ color:#444; font-size:20px; display:block; margin-bottom:5px; font-weight:bold; }
90
+ .number .desc{ color:#999; font-size:12px; margin-bottom:5px; display:block; white-space:nowrap; }
91
+ .number:last-child{ border-right:none; }
92
+ .numbers_container{ padding-right:0px; width:215px; float:left; margin:15px 0 -1px 20px; padding-bottom:5px; border-bottom:1px solid #dedede; }
93
+ .numbers_container.size_3{ width:258px; }
94
+ .numbers_container .title{ padding:4px 0 1px 0; color:#333; font-size:14px; display:block;font-weight:400; }
86
95
 
87
96
 
88
97
  ul.session_list{ list-style-type:none; margin:0; padding:9px 16px 0 11px; }
@@ -118,3 +127,4 @@ ul.event_type_list li .history:hover{ color:#333; text-decoration:underline; }
118
127
  height: 0;
119
128
  }
120
129
 
130
+ .highcharts-series circle{ stroke-width:1px; }
@@ -0,0 +1,1069 @@
1
+ var FnordMetric = (function(){
2
+
3
+ var canvasElem = false;
4
+
5
+ var currentNamespace = false;
6
+ var currentView = false;
7
+ var currentWidgetUID=23;
8
+
9
+ function decPrint(val){
10
+ return (val < 10 ? '0'+val : val);
11
+ }
12
+
13
+ function formatTimeOfDay(_time){
14
+ var time = new Date();
15
+ time.setTime(_time*1000);
16
+ return decPrint(time.getHours()) + ':' +
17
+ decPrint(time.getMinutes()) + ':' +
18
+ decPrint(time.getSeconds());
19
+ }
20
+
21
+ function formatTimeRange(range){
22
+ if (range < 60){
23
+ return parseInt(range) + ' sec';
24
+ } else if(range<3600){
25
+ return parseInt(range/60) + ' min';
26
+ } else if(range==3600){
27
+ return '1 hour';
28
+ } else if(range<(3600*24)){
29
+ return parseInt(range/3600) + ' hours';
30
+ } else if(range==(3600*24)){
31
+ return '1 day';
32
+ } else {
33
+ return parseInt(range/(3600*24)) + ' days';
34
+ }
35
+ }
36
+
37
+ function formatTimeSince(time){
38
+ var now = new Date().getTime()/1000;
39
+ var since = now - time;
40
+ return formatTimeRange(since);
41
+ }
42
+
43
+ function formatOffset(offset, next_offset){
44
+ if((offset == 0) && (next_offset==(3600*24))){
45
+ return 'today';
46
+ } if((offset == 0) && (next_offset==3600)){
47
+ return 'this hour';
48
+ } else if(offset == 0){
49
+ return 'last ' + formatTimeRange(next_offset||0);
50
+ } else if(offset==(3600*24)){
51
+ return 'yesterday';
52
+ } else if(offset==3600){
53
+ return 'last hour';
54
+ } else {
55
+ return formatTimeRange(offset) + ' ago';
56
+ }
57
+ }
58
+
59
+ function formatValue(value){
60
+ if(value < 10){
61
+ return value.toFixed(2);
62
+ } else if(value > 1000){
63
+ return (value/1000.0).toFixed(1) + "k";
64
+ } else {
65
+ return value.toFixed(0);
66
+ }
67
+ }
68
+
69
+ function getNextWidgetUID(){
70
+ return (currentWidgetUID += 1);
71
+ }
72
+
73
+ var toplistWidget = function(){
74
+
75
+ function render(opts){
76
+
77
+ var current_gauge = false;
78
+
79
+ var headbar = $('<div class="headbar"></div>').append(
80
+ $('<h2></h2>').html(opts.title)
81
+ );
82
+
83
+ opts.elem.append(headbar).css({
84
+ 'marginBottom': 20,
85
+ 'overflow': 'hidden'
86
+ }).append(
87
+ $('<div class="toplist_inner"></div>')
88
+ );
89
+
90
+ var first = true;
91
+ for(k in opts.gauges){
92
+ headbar.append(
93
+ $('<div></div>')
94
+ .attr('class', 'button mr')
95
+ .attr('rel', k)
96
+ .append(
97
+ $('<span></span>').html(opts.gauges[k].title)
98
+ ).click(function(){
99
+ loadGauge($(this).attr('rel'));
100
+ }
101
+ )
102
+ );
103
+ if(first){
104
+ first = false;
105
+ loadGauge(k);
106
+ }
107
+ }
108
+
109
+ if(opts.autoupdate){
110
+ var secs = parseInt(opts.autoupdate);
111
+ if(secs > 0){
112
+
113
+ var autoupdate_interval = window.setInterval(function(){
114
+ loadGauge(false, true);
115
+ }, secs*1000);
116
+
117
+ $('body').bind('fm_dashboard_close', function(){
118
+ window.clearInterval(autoupdate_interval);
119
+ });
120
+ }
121
+ };
122
+
123
+ function loadGauge(gkey, silent){
124
+ if(!gkey){ gkey = current_gauge; }
125
+ current_gauge = gkey;
126
+ if(!silent){ $('.toplist_inner', opts.elem).addClass('loading'); }
127
+ var _url = '/' + currentNamespace + '/gauge/' + gkey;
128
+ $.get(_url, function(_resp){
129
+ var resp = JSON.parse(_resp);
130
+ renderGauge(gkey, resp);
131
+ })
132
+ }
133
+
134
+ function renderGauge(gkey, gdata){
135
+ var _elem = $('.toplist_inner', opts.elem).removeClass('loading').html('');
136
+ $(gdata.values).each(function(n, _gd){
137
+ var _perc = (parseInt(gdata.values[n][1]) / parseFloat(gdata.count))*100;
138
+ var _item = $('<div class="toplist_item"><div class="title"></div><div class="value"></div><div class="percent"></div></div>');
139
+ $('.title', _item).html(gdata.values[n][0]);
140
+ $('.value', _item).html(formatValue(parseInt(gdata.values[n][1])));
141
+ $('.percent', _item).html(_perc.toFixed(1) + '%');
142
+ _elem.append(_item);
143
+ });
144
+ }
145
+
146
+ }
147
+
148
+
149
+ return {
150
+ render: render
151
+ };
152
+
153
+ };
154
+
155
+ var numbersWidget = function(){
156
+
157
+
158
+ function render(opts){
159
+
160
+ opts.elem.append(
161
+ $('<div class="headbar small"></div>').html(opts.title)
162
+ ).css({
163
+ 'marginBottom': 20,
164
+ 'overflow': 'hidden'
165
+ });
166
+
167
+ for(k in opts.gauges){
168
+ var gtick = parseInt(opts.gauges[k].tick);
169
+ var gtitle = opts.gauges[k].title;
170
+
171
+ var container = $('<div></div>')
172
+ .addClass('numbers_container')
173
+ .addClass('size_'+opts.offsets.length)
174
+ .attr('rel', k)
175
+ .append(
176
+ $('<div></div>')
177
+ .addClass('title')
178
+ .html(gtitle)
179
+ );
180
+
181
+
182
+ $(opts.offsets).each(function(n, offset){
183
+ var _off, _nextoff, _sum;
184
+ if (offset[0]=="s"){
185
+ _off = 0;
186
+ _sum = _nextoff = (gtick * parseInt(offset.slice(1)));
187
+ } else {
188
+ _sum = 0;
189
+ _off = offset*gtick;
190
+ _nextoff = gtick;
191
+ }
192
+ container.append(
193
+ $('<div></div>')
194
+ .addClass('number')
195
+ .attr('rel', k)
196
+ .attr('data-offset', _off)
197
+ .attr('data-sum', _sum)
198
+ .attr('data',0)
199
+ .append(
200
+ $('<span></span>').addClass('desc').html(formatOffset(_off, _nextoff))
201
+ )
202
+ .append(
203
+ $('<span></span>').addClass('value').html(0)
204
+ )
205
+ );
206
+ });
207
+
208
+ opts.elem.append(container);
209
+ }
210
+
211
+ if(opts.autoupdate){
212
+ var secs = parseInt(opts.autoupdate);
213
+ if(secs > 0){
214
+
215
+ var autoupdate_interval = window.setInterval(function(){
216
+ updateValues(opts);
217
+ }, secs*1000);
218
+
219
+ $('body').bind('fm_dashboard_close', function(){
220
+ window.clearInterval(autoupdate_interval);
221
+ });
222
+
223
+ }
224
+
225
+ };
226
+
227
+ updateValues(opts);
228
+
229
+ }
230
+
231
+ function updateValues(opts){
232
+ var values = $('.number', $(opts.elem));
233
+ var values_pending = values.length;
234
+ values.each(function(){
235
+ var _sum = parseInt($(this).attr('data-sum'));
236
+ var num = this;
237
+ var at = parseInt(new Date().getTime()/1000);
238
+ var url = '/' + currentNamespace + '/gauge/' + $(this).attr('rel');
239
+ if(_sum > 0){
240
+ url += '?at='+(at-_sum)+'-'+at+'&sum=true';
241
+ } else {
242
+ at -= parseInt($(this).attr('data-offset'));
243
+ url += '?at='+at;
244
+ }
245
+
246
+ $.get(url, function(_resp){
247
+ var resp = JSON.parse(_resp);
248
+ for(_k in resp){
249
+ $(num).attr('data', (resp[_k]||0));
250
+ }
251
+ if((values_pending -= 1)==0){
252
+ updateDisplay(opts, 4);
253
+ }
254
+ });
255
+ });
256
+ }
257
+
258
+ function updateDisplay(opts, diff_factor){
259
+ var still_running = false;
260
+ $('.number', $(opts.elem)).each(function(){
261
+ var target_val = parseFloat($(this).attr('data'));
262
+ var current_val = parseFloat($(this).attr('data-current'));
263
+ if(!current_val){ current_val=0; }
264
+ var diff = (target_val-current_val)/diff_factor;
265
+ if(diff < 1){ diff=1; }
266
+ if(target_val > current_val){
267
+ still_running = true;
268
+ var new_val = current_val+diff;
269
+ if(new_val > target_val){ new_val = target_val; }
270
+ $(this).attr('data-current', new_val);
271
+ $('.value', this).html(formatValue(new_val));
272
+ }
273
+ });
274
+ if(still_running){
275
+ (function(df){
276
+ window.setTimeout(function(){ updateDisplay(opts, df); }, 30);
277
+ })(diff_factor);
278
+ }
279
+ }
280
+
281
+ return {
282
+ render: render
283
+ };
284
+
285
+ };
286
+
287
+ var timelineWidget = function(){
288
+
289
+ function render(opts){
290
+
291
+ var widget_uid = getNextWidgetUID();
292
+ var chart=false;
293
+ var max_y=0;
294
+
295
+ function redrawWithRange(first_time, silent){
296
+ if(!silent){ $(opts.elem).css('opacity', 0.5); }
297
+ redrawDatepicker();
298
+ var _query = '?at='+opts.start_timestamp+'-'+opts.end_timestamp;
299
+ //chart.series = [];
300
+ max_y=0;
301
+ //metrics_completed = 0;
302
+ $(opts.gauges).each(function(i,gauge){
303
+ $.ajax({
304
+ url: '/'+currentNamespace+'/gauge/'+gauge+_query,
305
+ success: redrawGauge(first_time, gauge)
306
+ });
307
+ });
308
+ }
309
+
310
+ function redrawGauge(first_time, gauge){
311
+ return (function(json){
312
+ var raw_data = JSON.parse(json);
313
+ var series_data = [];
314
+
315
+ for(p in raw_data){
316
+ series_data.push([parseInt(p)*1000, raw_data[p]||0]);
317
+ max_y = Math.max(max_y, raw_data[p]);
318
+ }
319
+
320
+ if(!first_time){
321
+ chart.get('series-'+gauge).setData(series_data);
322
+ } else {
323
+ chart.addSeries({
324
+ name: opts.gauge_titles[gauge],
325
+ data: series_data,
326
+ id: 'series-'+gauge
327
+ });
328
+ }
329
+
330
+ chart.yAxis[0].setExtremes(0,max_y);
331
+ chart.redraw();
332
+
333
+ // shown on the *first* gauge load
334
+ $(opts.elem).css('opacity', 1);
335
+ });
336
+ }
337
+
338
+ function redrawDatepicker(){
339
+ $('.datepicker', opts.elem).html(
340
+ Highcharts.dateFormat('%d.%m.%y %H:%M', parseInt(opts.start_timestamp)*1000) +
341
+ '&nbsp;&dash;&nbsp;' +
342
+ Highcharts.dateFormat('%d.%m.%y %H:%M', parseInt(opts.end_timestamp)*1000)
343
+ );
344
+ }
345
+
346
+ function moveRange(direction){
347
+ v = opts.tick*direction*8;
348
+ opts.start_timestamp += v;
349
+ opts.end_timestamp += v;
350
+ redrawWithRange();
351
+ }
352
+
353
+ function drawLayout(){
354
+ $(opts.elem).append( $('<div></div>').attr('class', 'headbar').append(
355
+ $('<div></div>').attr('class', 'button mr').append($('<span></span>').html('refresh')).click(
356
+ function(){ redrawWithRange(); }
357
+ )
358
+ ).append(
359
+ $('<div></div>').attr('class', 'button mr').append($('<span></span>').html('&rarr;')).click(
360
+ function(){ moveRange(1); }
361
+ )
362
+ ).append(
363
+ $('<div></div>').attr('class', 'datepicker')
364
+ ).append(
365
+ $('<div></div>').attr('class', 'button').append($('<span></span>').html('&larr;')).click(
366
+ function(){ moveRange(-1); }
367
+ )
368
+ ).append(
369
+ $('<h2></h2>').html(opts.title)
370
+ ) ).append(
371
+ $('<div></div>').attr('id', 'container-'+widget_uid).css({
372
+ height: 256,
373
+ marginBottom: 20,
374
+ overflow: 'hidden'
375
+ })
376
+ );
377
+ }
378
+
379
+ function drawChart(){
380
+ chart = new Highcharts.Chart({
381
+ chart: {
382
+ renderTo: 'container-'+widget_uid,
383
+ defaultSeriesType: opts.plot_style,
384
+ height: 270
385
+ },
386
+ series: [],
387
+ title: { text: '' },
388
+ xAxis: {
389
+ type: 'datetime',
390
+ tickInterval: opts.tick * 1000,
391
+ title: (opts.x_title||''),
392
+ labels: { step: 2 }
393
+ },
394
+ yAxis: {
395
+ title: (opts.y_title||''),
396
+ min: 0,
397
+ max: 1000
398
+ },
399
+ legend: {
400
+ layout: 'horizontal',
401
+ align: 'top',
402
+ verticalAlign: 'top',
403
+ x: -5,
404
+ y: -3,
405
+ margin: 25,
406
+ borderWidth: 0
407
+ },
408
+ plotOptions: {
409
+ line: {
410
+ shadow: false,
411
+ lineWidth: 3
412
+ }
413
+ }
414
+ });
415
+ }
416
+
417
+ drawLayout();
418
+ drawChart();
419
+
420
+ redrawWithRange(true);
421
+
422
+ if(opts.autoupdate){
423
+ var secs = parseInt(opts.autoupdate);
424
+ if(secs > 0){
425
+
426
+ var autoupdate_interval = window.setInterval(function(){
427
+ if(
428
+ (parseInt(new Date().getTime()/1000) - opts.end_timestamp) >
429
+ (opts.include_current ? 0 : opts.tick)
430
+ ){
431
+ opts.end_timestamp += opts.tick;
432
+ opts.start_timestamp += opts.tick;
433
+ }
434
+
435
+ redrawWithRange(false, true);
436
+ }, secs*1000);
437
+
438
+ $('body').bind('fm_dashboard_close', function(){
439
+ window.clearInterval(autoupdate_interval);
440
+ });
441
+
442
+ }
443
+ };
444
+
445
+ }
446
+
447
+ return {
448
+ render: render
449
+ };
450
+
451
+ };
452
+
453
+
454
+ var barsWidget = function(){
455
+
456
+ function render(opts){
457
+
458
+ var widget_uid = getNextWidgetUID();
459
+ var chart=false;
460
+ var max_y=0;
461
+
462
+ function redraw(first_time, silent){
463
+ if(!silent){ $(opts.elem).css('opacity', 0.5); }
464
+ max_y=0;
465
+ $(opts.gauges).each(function(i,gauge){
466
+ $.ajax({
467
+ url: '/'+currentNamespace+'/gauge/'+gauge,
468
+ success: redrawGauge(first_time, gauge)
469
+ });
470
+ });
471
+ }
472
+
473
+ function redrawGauge(first_time, gauge){
474
+ return (function(json){
475
+ var raw_data = JSON.parse(json);
476
+ var series_data = [];
477
+ var series_type;
478
+ var label_data = [];
479
+
480
+ if(opts.plot_style == 'horizontal'){
481
+ series_type = 'bar';
482
+ } else {
483
+ series_type = 'column';
484
+ }
485
+
486
+ if(opts.order_by == 'field'){
487
+ raw_data.values.sort(function(a,b){
488
+ if(a[0] == b[0]){
489
+ return 0;
490
+ }else if(a[0] > b[0]){
491
+ return 1;
492
+ } else {
493
+ return -1;
494
+ }
495
+ });
496
+ }
497
+
498
+ for(p in raw_data.values){
499
+ label_data.push(raw_data.values[p][0]||'?');
500
+ series_data.push(parseInt(raw_data.values[p][1]||0));
501
+ //max_y = Math.max(max_y, raw_data[p]);
502
+ }
503
+
504
+ chart = new Highcharts.Chart({
505
+ chart: {
506
+ renderTo: 'container-'+widget_uid,
507
+ defaultSeriesType: series_type,
508
+ height: 270
509
+ },
510
+ title: { text: '' },
511
+ xAxis: {
512
+ categories: label_data
513
+ },
514
+ yAxis: {
515
+ title: { text: '' }
516
+ },
517
+ legend: {
518
+ layout: 'horizontal',
519
+ align: 'top',
520
+ verticalAlign: 'top',
521
+ x: -5,
522
+ y: -3,
523
+ margin: 25,
524
+ borderWidth: 0
525
+ },
526
+ plotOptions: {
527
+ column: {
528
+ animation: first_time,
529
+ }
530
+ },
531
+ series: [
532
+ {
533
+ name: opts.gauge_titles[gauge],
534
+ data: series_data
535
+ }
536
+ ]
537
+ });
538
+
539
+ //chart.redraw();
540
+ $(opts.elem).css('opacity', 1);
541
+ });
542
+ }
543
+
544
+ function drawLayout(){
545
+ $(opts.elem).append( $('<div></div>').attr('class', 'headbar').append(
546
+ $('<div></div>').attr('class', 'button mr').append($('<span></span>').html('refresh')).click(
547
+ function(){ redraw(); }
548
+ )
549
+ ).append(
550
+ $('<h2></h2>').html(opts.title)
551
+ ) ).append(
552
+ $('<div></div>').attr('id', 'container-'+widget_uid).css({
553
+ height: 256,
554
+ marginBottom: 20,
555
+ overflow: 'hidden'
556
+ })
557
+ );
558
+ }
559
+
560
+ drawLayout();
561
+ redraw(true);
562
+
563
+ if(opts.autoupdate){
564
+ var autoupdate_interval = window.setInterval(function(){
565
+ redraw(false, true);
566
+ }, opts.autoupdate*1000);
567
+
568
+ $('body').bind('fm_dashboard_close', function(){
569
+ window.clearInterval(autoupdate_interval);
570
+ });
571
+ }
572
+
573
+ }
574
+
575
+ return {
576
+ render: render
577
+ };
578
+
579
+ };
580
+
581
+
582
+ var pieWidget = function(){
583
+
584
+ function render(opts){
585
+
586
+ var widget_uid = getNextWidgetUID();
587
+ var chart=false;
588
+
589
+ function redraw(first_time, silent){
590
+ if(!silent){ $(opts.elem).css('opacity', 0.5); }
591
+ var gauge_values = {};
592
+ var gauges_left = opts.gauges.length;
593
+ var at = parseInt(new Date().getTime()/1000);
594
+ $(opts.gauges).each(function(i,gauge){
595
+ $.ajax({
596
+ url: '/'+currentNamespace+'/gauge/'+gauge+'?at='+at,
597
+ success: function(_resp){
598
+ var resp = JSON.parse(_resp);
599
+ gauges_left -= 1;
600
+ for(_tk in resp){
601
+ gauge_values[gauge] = parseInt(resp[_tk]||0);
602
+ }
603
+ if(gauges_left==0){
604
+ redrawChart(first_time, gauge_values);
605
+ }
606
+ }
607
+ });
608
+ });
609
+ }
610
+
611
+ function redrawChart(first_time, gauge_values){
612
+
613
+ var series_data = [];
614
+
615
+ for(_gkey in gauge_values){
616
+ series_data.push([
617
+ opts.gauge_titles[_gkey],
618
+ gauge_values[_gkey]
619
+ ]);
620
+ }
621
+
622
+ chart = new Highcharts.Chart({
623
+ chart: {
624
+ renderTo: 'container-'+widget_uid,
625
+ defaultSeriesType: 'pie',
626
+ height: 270,
627
+ spacingTop: 5,
628
+ spacingBottom: 30
629
+ },
630
+ credits: {
631
+ enabled: false
632
+ },
633
+ title: { text: '' },
634
+ legend: {
635
+ layout: 'horizontal',
636
+ align: 'top',
637
+ verticalAlign: 'top',
638
+ margin: 25,
639
+ borderWidth: 0
640
+ },
641
+ tooltip: {
642
+ formatter: function() {
643
+ return '<b>'+ this.point.name +'</b>: '+ this.y + ' (' + this.percentage.toFixed(1) + '%)';
644
+ }
645
+ },
646
+ plotOptions: {
647
+ pie: {
648
+ animation: first_time,
649
+ dataLabels: {
650
+ formatter: function() {
651
+ return '<b>'+ this.point.name +'</b><br />'+ this.percentage.toFixed(1) +' %';
652
+ }
653
+ }
654
+ }
655
+ },
656
+ series: [
657
+ {
658
+ name: opts.title,
659
+ data: series_data
660
+ }
661
+ ]
662
+ });
663
+
664
+ //chart.redraw();
665
+ $(opts.elem).css('opacity', 1);
666
+ }
667
+
668
+ function drawLayout(){
669
+ $(opts.elem).append( $('<div></div>').attr('class', 'headbar small').append(
670
+ $('<span></span>').html(opts.title)
671
+ ) ).append(
672
+ $('<div></div>').attr('id', 'container-'+widget_uid).css({
673
+ height: 270
674
+ })
675
+ );
676
+ }
677
+
678
+ drawLayout();
679
+ redraw(true);
680
+
681
+ if(opts.autoupdate){
682
+ var autoupdate_interval = window.setInterval(function(){
683
+ redraw(false, true);
684
+ }, opts.autoupdate*1000);
685
+
686
+ $('body').bind('fm_dashboard_close', function(){
687
+ window.clearInterval(autoupdate_interval);
688
+ });
689
+ }
690
+
691
+ }
692
+
693
+ return {
694
+ render: render
695
+ };
696
+
697
+ };
698
+
699
+
700
+ var sessionView = (function(){
701
+
702
+ var listElem = $('<ul class="session_list"></ul>');
703
+ var feedInnerElem = $('<ul class="feed_inner"></ul>');
704
+ var typeListElem = $('<ul class="event_type_list"></ul>');
705
+ var filterElem = $('<div class="events_sidebar"></div>').html(
706
+ $('<div class="headbar small"></div>').html('Event Types')
707
+ ).append(typeListElem);
708
+ var feedElem = $('<div class="sessions_feed"></div>').html(
709
+ $('<div class="headbar small"></div>').html('Event Feed')
710
+ ).append(feedInnerElem);
711
+ var sideElem = $('<div class="sessions_sidebar"></div>').html(
712
+ $('<div class="headbar small"></div>').html('Active Users')
713
+ ).append(listElem);
714
+
715
+ var eventsPolledUntil = false;
716
+ var eventsFilter = [];
717
+ var sessionData = {};
718
+ var pollRunning = true;
719
+
720
+ function load(elem){
721
+ eventsPolledUntil = parseInt(new Date().getTime()/10000);
722
+ elem.html('')
723
+ .append(filterElem)
724
+ .append(feedElem)
725
+ .append(sideElem);
726
+ startPoll();
727
+ loadEventTypes();
728
+ };
729
+
730
+ function resize(_width, _height){
731
+ $('.sessions_feed').width(_width-452);
732
+ };
733
+
734
+ function startPoll(){
735
+ (doSessionPoll())();
736
+ (doEventsPoll())();
737
+ sessionView.session_poll = window.setInterval(doSessionPoll(), 1000);
738
+ };
739
+
740
+ function stopPoll(){
741
+ pollRunning = false;
742
+ window.clearInterval(sessionView.session_poll);
743
+ }
744
+
745
+ function doSessionPoll(){
746
+ return (function(){
747
+ $.ajax({
748
+ url: '/'+currentNamespace+'/sessions',
749
+ success: callbackSessionPoll()
750
+ });
751
+ });
752
+ };
753
+
754
+ function loadEventHistory(event_type){
755
+ feedInnerElem.html('');
756
+ $.ajax({
757
+ url: '/'+currentNamespace+'/events?type='+event_type,
758
+ success: function(_data, _status){
759
+ var data = JSON.parse(_data).events;
760
+ for(var n=data.length; n >= 0; n--){
761
+ if(data[n]){ renderEvent(data[n]); }
762
+ }
763
+ }
764
+ });
765
+ }
766
+
767
+ function callbackSessionPoll(){
768
+ return (function(_data, _status){
769
+ $.each(JSON.parse(_data).sessions, function(i,v){
770
+ updateSession(v);
771
+ });
772
+ sortSessions();
773
+ });
774
+ };
775
+
776
+ function loadEventTypes(){
777
+ $.ajax({
778
+ url: '/'+currentNamespace+'/event_types',
779
+ success: function(_data){
780
+ var data = JSON.parse(_data);
781
+ $(data.types).each(function(i,v){
782
+ if(v.slice(0,5)!='_set_'){ addEventType(v,v); }
783
+ });
784
+ }
785
+ });
786
+ };
787
+
788
+ function addEventType(type, display){
789
+ typeListElem.append(
790
+ $('<li class="event_type"></li>').append(
791
+ $('<span class="history"></span>').html('history')
792
+ .click(function(){
793
+ $('.event_type_list .event_type input').attr('checked', false);
794
+ $('input', $(this).parent()).attr('checked', true);
795
+ updateEventFilter(); loadEventHistory(type);
796
+ })
797
+ ).append(
798
+ $('<input type="checkbox" />').attr('checked', true)
799
+ .click(function(){ updateEventFilter(); })
800
+ ).append(
801
+ $('<span></span>').html(display)
802
+ ).attr('rel', type)
803
+ );
804
+ }
805
+
806
+ function updateEventFilter(){
807
+ var _unchecked_types = [];
808
+ $('ul.event_type_list li.event_type').each(function(i,v){
809
+ if(!$('input', v).attr('checked')){
810
+ _unchecked_types.push($(v).attr('rel'));
811
+ }
812
+ });
813
+ eventsFilter = _unchecked_types;
814
+ }
815
+
816
+ function doEventsPoll(){
817
+ return (function(){
818
+ $.ajax({
819
+ url: '/'+currentNamespace+'/events?since='+eventsPolledUntil,
820
+ success: callbackEventsPoll()
821
+ });
822
+ });
823
+ };
824
+
825
+ function callbackEventsPoll(){
826
+ return (function(_data, _status){
827
+ var data = JSON.parse(_data)
828
+ var events = data.events;
829
+ var timout = 1000;
830
+ var maxevents = 200;
831
+ if(events.length > 0){
832
+ timeout = 200;
833
+ eventsPolledUntil = parseInt(events[0]._time)-1;
834
+ }
835
+ for(var n=events.length-1; n >= 0; n--){
836
+ var v = events[n];
837
+ if(eventsFilter.indexOf(v._type) == -1){
838
+ if(parseInt(v._time)<=eventsPolledUntil){
839
+ renderEvent(v);
840
+ }
841
+ }
842
+ };
843
+ var elems = $("p", feedInnerElem);
844
+ for(var n=maxevents; n < elems.length; n++){
845
+ $(elems[n]).remove();
846
+ }
847
+ if(pollRunning){
848
+ window.setTimeout(doEventsPoll(), timout);
849
+ }
850
+ });
851
+ };
852
+
853
+ function updateSession(session_data){
854
+ sessionData[session_data.session_key] = session_data;
855
+ renderSession(session_data);
856
+ }
857
+
858
+ function sortSessions(){
859
+ console.log("fixme: sort and splice to 100");
860
+ }
861
+
862
+ function renderSession(session_data){
863
+
864
+ var session_name = session_data["_name"];
865
+ var session_time = formatTimeSince(session_data["_updated_at"]);
866
+ var session_elem = $('li[data-session='+session_data["session_key"]+']:first');
867
+
868
+ if(session_elem.length>0){
869
+
870
+ if(session_data["_picture"] && (session_data["_picture"].length > 1)){
871
+ $('.picture img', session_elem).attr('src', session_data["_picture"])
872
+ }
873
+
874
+ if(session_name){
875
+ $('.name', session_elem).html(session_name);
876
+ }
877
+
878
+ $('.time', session_elem).html(session_time);
879
+
880
+ } else {
881
+
882
+ var session_picture = $('<img width="18" />');
883
+
884
+ if(!session_name){
885
+ session_name = session_data["session_key"].substr(0,15)
886
+ };
887
+
888
+ if(session_data["_picture"]){
889
+ session_picture.attr('src', session_data["_picture"]);
890
+ };
891
+
892
+ listElem.append(
893
+ $('<li class="session"></li>').append(
894
+ $('<div class="picture"></div>').html(session_picture)
895
+ ).append(
896
+ $('<span class="name"></span>').html(session_name)
897
+ ).append(
898
+ $('<span class="time"></span>').html(session_time)
899
+ ).attr('data-session', session_data["session_key"])
900
+ );
901
+
902
+ }
903
+ };
904
+
905
+ function renderEvent(event_data){
906
+ var event_time = $('<span class="time"></span>');
907
+ var event_message = $('<span class="message"></span>');
908
+ var event_props = $('<span class="properties"></span>');
909
+ var event_picture = $('<div class="picture"></picture>');
910
+
911
+ var event_type = event_data._type;
912
+
913
+ if(!event_type){ return true; }
914
+
915
+ if(event_data._message){
916
+ event_message.html(event_data._message);
917
+ } else if(event_type=="_pageview"){
918
+ event_message.html("Pageview: " + event_data.url);
919
+ } else if(event_type.substr(0,5) == '_set_'){
920
+ return true; /* dont render */
921
+ } else {
922
+ event_message.html(event_type);
923
+ }
924
+
925
+ event_time.html(formatTimeOfDay(event_data._time));
926
+
927
+ if(event_data._session_key && event_data._session_key.length > 0){
928
+ if(session_data=sessionData[event_data._session_key]){
929
+ if(session_data._name){
930
+ event_props.append(
931
+ $('<strong></strong>').html(session_data._name)
932
+ );
933
+ }
934
+ if(session_data._picture){
935
+ event_picture.append(
936
+ $('<img width="40" />').attr('src', session_data._picture)
937
+ )
938
+ }
939
+ }
940
+ }
941
+
942
+ feedInnerElem.prepend(
943
+ $('<li class="feed_event"></li>')
944
+ .append(event_time)
945
+ .append(event_picture)
946
+ .append(event_message)
947
+ .append(event_props)
948
+ );
949
+ }
950
+
951
+ function close(){
952
+ stopPoll();
953
+ };
954
+
955
+ return {
956
+ load: load,
957
+ resize: resize,
958
+ close: close
959
+ };
960
+
961
+ });
962
+
963
+
964
+ var dashboardView = (function(dashboard_name){
965
+
966
+ var widgets = [];
967
+ var viewport = null;
968
+
969
+ function load(_viewport){
970
+ viewport = _viewport.html('');
971
+ $.ajax({
972
+ url: '/'+currentNamespace+'/dashboard/'+dashboard_name,
973
+ success: function(resp, status){
974
+ var conf = JSON.parse(resp);
975
+ renderWidgets(conf.widgets);
976
+ }
977
+ });
978
+ };
979
+
980
+ function renderWidgets(_widgets){
981
+ for(wkey in _widgets){
982
+ var widget = _widgets[wkey];
983
+ widget["elem"] = $('<div class="widget"></div>');
984
+ widgets[wkey] = widget;
985
+ viewport.append(widget.elem);
986
+ resizeWidget(wkey);
987
+ renderWidget(wkey);
988
+ };
989
+ resize();
990
+ };
991
+
992
+ function renderWidget(wkey){
993
+ var widget = widgets[wkey];
994
+ /* argh... */
995
+ if(widget.klass=='TimelineWidget'){ timelineWidget().render(widget); }
996
+ if(widget.klass=='BarsWidget'){ barsWidget().render(widget); }
997
+ if(widget.klass=='NumbersWidget'){ numbersWidget().render(widget); }
998
+ if(widget.klass=='ToplistWidget'){ toplistWidget().render(widget); }
999
+ if(widget.klass=='PieWidget'){ pieWidget().render(widget); }
1000
+ };
1001
+
1002
+ function resizeWidget(wkey){
1003
+ var widget = widgets[wkey];
1004
+ var wwperc = widgets[wkey].width;
1005
+ if(!wwperc){ wwperc = 100; }
1006
+ var wwidth = viewport.width() * (wwperc/100.0);
1007
+ if(wwperc==100){
1008
+ widgets[wkey].elem.addClass('full_width');
1009
+ } else { wwidth -= 1; }
1010
+ widget.elem.width(wwidth);
1011
+ }
1012
+
1013
+ function resize(){
1014
+ for(wkey in widgets){
1015
+ resizeWidget(wkey);
1016
+ };
1017
+ };
1018
+
1019
+ function close(){
1020
+ $('body').trigger('fm_dashboard_close');
1021
+ };
1022
+
1023
+ return {
1024
+ load: load,
1025
+ resize: resize,
1026
+ close: close
1027
+ };
1028
+
1029
+ });
1030
+
1031
+
1032
+ function renderDashboard(_dash){
1033
+ loadView(dashboardView(_dash));
1034
+ };
1035
+
1036
+ function renderSessionView(){
1037
+ loadView(sessionView());
1038
+ }
1039
+
1040
+ function loadView(_view){
1041
+ if(currentView){ currentView.close(); }
1042
+ canvasElem.html('loading!');
1043
+ currentView = _view;
1044
+ currentView.load(canvasElem);
1045
+ resizeView();
1046
+ };
1047
+
1048
+ function resizeView(){
1049
+ currentView.resize(
1050
+ canvasElem.innerWidth(),
1051
+ canvasElem.innerHeight()
1052
+ );
1053
+ };
1054
+
1055
+ function init(_namespace, _canvasElem){
1056
+ canvasElem = _canvasElem;
1057
+ currentNamespace = _namespace;
1058
+ loadView(sessionView());
1059
+ };
1060
+
1061
+ return {
1062
+ p: '/fnordmetric/',
1063
+ renderDashboard: renderDashboard,
1064
+ renderSessionView: renderSessionView,
1065
+ resizeView: resizeView,
1066
+ init: init
1067
+ };
1068
+
1069
+ })();