fnordmetric 0.5.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
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
+ })();