fnordmetric 0.7.5 → 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. data/doc/V1.0-ROADMAP +97 -0
  2. data/doc/full_example.rb +95 -511
  3. data/doc/legacy_example.rb +640 -0
  4. data/doc/minimal_example.rb +26 -0
  5. data/doc/preview3.png +0 -0
  6. data/fnordmetric.gemspec +3 -2
  7. data/lib/fnordmetric/acceptors/acceptor.rb +29 -0
  8. data/lib/fnordmetric/{inbound_stream.rb → acceptors/tcp_acceptor.rb} +8 -5
  9. data/lib/fnordmetric/{inbound_datagram.rb → acceptors/udp_acceptor.rb} +9 -8
  10. data/lib/fnordmetric/api.rb +2 -2
  11. data/lib/fnordmetric/context.rb +37 -18
  12. data/lib/fnordmetric/defaults.rb +9 -0
  13. data/lib/fnordmetric/ext.rb +72 -0
  14. data/lib/fnordmetric/gauge.rb +37 -10
  15. data/lib/fnordmetric/gauge_calculations.rb +38 -16
  16. data/lib/fnordmetric/gauge_modifiers.rb +67 -0
  17. data/lib/fnordmetric/gauge_rendering.rb +40 -0
  18. data/lib/fnordmetric/gauge_validations.rb +15 -0
  19. data/lib/fnordmetric/gauges/distribution_gauge.rb +85 -0
  20. data/lib/fnordmetric/gauges/timeseries_gauge.rb +143 -0
  21. data/lib/fnordmetric/gauges/toplist_gauge.rb +44 -0
  22. data/lib/fnordmetric/histogram.rb +57 -0
  23. data/lib/fnordmetric/logger.rb +42 -36
  24. data/lib/fnordmetric/namespace.rb +47 -23
  25. data/lib/fnordmetric/session.rb +6 -6
  26. data/lib/fnordmetric/standalone.rb +15 -35
  27. data/lib/fnordmetric/timeseries.rb +79 -0
  28. data/lib/fnordmetric/toplist.rb +61 -0
  29. data/lib/fnordmetric/version.rb +1 -1
  30. data/lib/fnordmetric/web/app.rb +122 -0
  31. data/lib/fnordmetric/web/app_helpers.rb +42 -0
  32. data/lib/fnordmetric/{dashboard.rb → web/dashboard.rb} +4 -0
  33. data/lib/fnordmetric/{event.rb → web/event.rb} +7 -2
  34. data/lib/fnordmetric/web/reactor.rb +87 -0
  35. data/lib/fnordmetric/web/web.rb +53 -0
  36. data/lib/fnordmetric/web/websocket.rb +38 -0
  37. data/lib/fnordmetric/widgets/bars_widget.rb +44 -0
  38. data/lib/fnordmetric/{html_widget.rb → widgets/html_widget.rb} +0 -0
  39. data/lib/fnordmetric/widgets/numbers_widget.rb +56 -0
  40. data/lib/fnordmetric/{pie_widget.rb → widgets/pie_widget.rb} +0 -0
  41. data/lib/fnordmetric/widgets/timeseries_widget.rb +55 -0
  42. data/lib/fnordmetric/widgets/toplist_widget.rb +64 -0
  43. data/lib/fnordmetric/worker.rb +26 -25
  44. data/lib/fnordmetric.rb +85 -115
  45. data/readme.md +362 -0
  46. data/spec/gauge_like_shared.rb +54 -0
  47. data/spec/gauge_spec.rb +2 -36
  48. data/spec/namespace_spec.rb +25 -11
  49. data/spec/spec_helper.rb +4 -0
  50. data/spec/{inbound_stream_spec.rb → tcp_acceptor_spec.rb} +3 -3
  51. data/spec/timeseries_gauge_spec.rb +54 -0
  52. data/spec/{inbound_datagram_spec.rb → udp_acceptor_spec.rb} +3 -3
  53. data/web/fnordmetric.css +786 -0
  54. data/web/haml/app.haml +38 -0
  55. data/web/haml/distribution_gauge.haml +118 -0
  56. data/web/haml/timeseries_gauge.haml +80 -0
  57. data/web/haml/toplist_gauge.haml +194 -0
  58. data/web/img/head.png +0 -0
  59. data/web/img/list.png +0 -0
  60. data/web/img/list_active.png +0 -0
  61. data/web/img/list_hover.png +0 -0
  62. data/web/img/loader_white.gif +0 -0
  63. data/web/img/navbar.png +0 -0
  64. data/web/img/navbar_btn.png +0 -0
  65. data/web/img/picto_gauge.png +0 -0
  66. data/web/js/fnordmetric.bars_widget.js +178 -0
  67. data/web/js/fnordmetric.dashboard_view.js +99 -0
  68. data/web/js/fnordmetric.gauge_view.js +260 -0
  69. data/web/js/fnordmetric.html_widget.js +21 -0
  70. data/web/js/fnordmetric.js +255 -0
  71. data/web/js/fnordmetric.numbers_widget.js +121 -0
  72. data/web/js/fnordmetric.overview_view.js +35 -0
  73. data/web/js/fnordmetric.pie_widget.js +118 -0
  74. data/web/js/fnordmetric.realtime_timeline_widget.js +175 -0
  75. data/web/js/fnordmetric.session_view.js +343 -0
  76. data/web/js/fnordmetric.timeline_widget.js +333 -0
  77. data/web/js/fnordmetric.timeseries_widget.js +388 -0
  78. data/web/js/fnordmetric.toplist_widget.js +112 -0
  79. data/web/js/fnordmetric.ui.js +91 -0
  80. data/web/js/fnordmetric.util.js +244 -0
  81. data/{pub → web}/loader.gif +0 -0
  82. data/web/vendor/d3.v2.js +9382 -0
  83. data/web/vendor/font-awesome/css/font-awesome.css +239 -0
  84. data/web/vendor/font-awesome/font/fontawesome-webfont.eot +0 -0
  85. data/web/vendor/font-awesome/font/fontawesome-webfont.svg +175 -0
  86. data/web/vendor/font-awesome/font/fontawesome-webfont.svgz +0 -0
  87. data/web/vendor/font-awesome/font/fontawesome-webfont.ttf +0 -0
  88. data/web/vendor/font-awesome/font/fontawesome-webfont.woff +0 -0
  89. data/web/vendor/jquery-1.6.2.min.js +18 -0
  90. data/web/vendor/jquery-ui.min.js +413 -0
  91. data/web/vendor/jquery.maskedinput.js +252 -0
  92. data/web/vendor/rickshaw.css +286 -0
  93. data/web/vendor/rickshaw.fnordmetric.js +2676 -0
  94. metadata +129 -79
  95. data/Gemfile +0 -6
  96. data/README.md +0 -404
  97. data/Rakefile +0 -6
  98. data/doc/version +0 -1
  99. data/haml/app.haml +0 -79
  100. data/haml/widget.haml +0 -9
  101. data/lib/fnordmetric/app.rb +0 -163
  102. data/lib/fnordmetric/average_metric.rb +0 -7
  103. data/lib/fnordmetric/bars_widget.rb +0 -26
  104. data/lib/fnordmetric/combine_metric.rb +0 -7
  105. data/lib/fnordmetric/count_metric.rb +0 -13
  106. data/lib/fnordmetric/funnel_widget.rb +0 -2
  107. data/lib/fnordmetric/metric.rb +0 -80
  108. data/lib/fnordmetric/metric_api.rb +0 -37
  109. data/lib/fnordmetric/numbers_widget.rb +0 -26
  110. data/lib/fnordmetric/report.rb +0 -29
  111. data/lib/fnordmetric/sum_metric.rb +0 -13
  112. data/lib/fnordmetric/timeline_widget.rb +0 -30
  113. data/lib/fnordmetric/toplist_widget.rb +0 -25
  114. data/pub/fnordmetric.css +0 -145
  115. data/pub/fnordmetric.js +0 -1179
  116. data/pub/vendor/highcharts.js +0 -170
  117. data/pub/vendor/jquery-1.6.1.min.js +0 -18
@@ -0,0 +1,388 @@
1
+ FnordMetric.widgets.timeseriesWidget = function(){
2
+
3
+ /*
4
+ options:
5
+
6
+ height: 430
7
+ default_cardinal: true/false
8
+ default_style: line/area/stack/flow
9
+ series
10
+ */
11
+
12
+ var widget_uid = "fnord-" + parseInt(Math.random()*99990000);
13
+ var width, height, opts, graph, gconfig, legend, hoverDetail, shelving, highlighter, resolution;
14
+
15
+ var cardinal = true;
16
+ var xticks = 30;
17
+
18
+ function render(_opts){
19
+ opts = _opts;
20
+
21
+ if(opts.default_style == "areaspline"){ opts.default_style = 'area'; }
22
+ if(!opts.default_style){ opts.default_style = 'line'; }
23
+ if(!opts.default_cardinal){ opts.default_cardinal = true; }
24
+
25
+ draw_layout();
26
+
27
+ gconfig = {
28
+ element: $('.container', opts.elem)[0],
29
+ interpolation: (opts.default_cardinal ? 'cardinal' : 'linear'),
30
+ stroke: true,
31
+ series: opts.series,
32
+ padding: { top: 0.1, bottom: 0 }
33
+ }
34
+
35
+ $(opts.elem)
36
+ .addClass('resize_listener')
37
+ .bind('fm_resize', renderChart);
38
+
39
+ apply_style(opts.default_style);
40
+
41
+ if(opts.async_chart){
42
+ updateChart();
43
+ } else {
44
+ renderChart();
45
+ }
46
+ }
47
+
48
+ function toggle_cardinal(){
49
+ if($(this).hasClass('active')){ return false; }
50
+ $(this).addClass('active').siblings().removeClass('active');
51
+
52
+ if(cardinal === true){
53
+ cardinal = false;
54
+ gconfig.interpolation = 'linear';
55
+ } else {
56
+ cardinal = true;
57
+ gconfig.interpolation = 'cardinal';
58
+ }
59
+
60
+ graph.configure(gconfig);
61
+ graph.render();
62
+ }
63
+
64
+ function change_style(){
65
+ $(this).addClass('active').siblings().removeClass('active');
66
+
67
+ apply_style($(this).attr('data'));
68
+ //graph.configure(gconfig);
69
+ //graph.render();
70
+
71
+ renderChart();
72
+ }
73
+
74
+ function apply_style(style){
75
+ if(style == 'line'){
76
+ gconfig.renderer = 'line';
77
+ gconfig.offset = 'zero';
78
+ }
79
+
80
+ if(style == 'area'){
81
+ gconfig.renderer = 'area';
82
+ gconfig.offset = 'zero';
83
+ }
84
+
85
+ if(style == 'flow'){
86
+ gconfig.renderer = 'stack';
87
+ gconfig.offset = 'silhouette';
88
+ }
89
+ }
90
+
91
+ function apply_resolution(){
92
+ if(!resolution){
93
+ var trgt_resolution = 50;
94
+ var best_resolution = false;
95
+
96
+ for(ind in opts.series_resolutions){
97
+ var _diff = Math.abs(
98
+ trgt_resolution -
99
+ (opts.timespan / opts.series_resolutions[ind])
100
+ );
101
+
102
+ if((!best_resolution) || (_diff < best_resolution)){
103
+ best_resolution = opts.series_resolutions[ind];
104
+ }
105
+ }
106
+
107
+ resolution = best_resolution;
108
+ }
109
+
110
+ for(ind in gconfig.series){
111
+ gconfig.series[ind].data = gconfig.series[ind]["data"+resolution];
112
+ }
113
+
114
+ $('.button.tick', opts.elem)
115
+ .removeClass('active')
116
+ .filter('[data-tick="'+resolution+'"]')
117
+ .addClass('active');
118
+ }
119
+
120
+ function draw_layout(){
121
+ if(!opts.no_headbar){
122
+ $(opts.elem).append(
123
+ $('<div></div>')
124
+ .addClass('headbar')
125
+ .append($('<h2></h2>').html(opts.title))
126
+ .append(
127
+ $('<div class="btn_group mr"></div>')
128
+ .append(
129
+ $('<div></div>')
130
+ .addClass('button')
131
+ .append($('<span>').html('Flow'))
132
+ .attr('data', 'flow')
133
+ .click(change_style)
134
+ )
135
+ .append(
136
+ $('<div></div>')
137
+ .addClass('button')
138
+ .append($('<span>').html('Area'))
139
+ .attr('data', 'area')
140
+ .click(change_style)
141
+ )
142
+ .append(
143
+ $('<div></div>')
144
+ .addClass('button')
145
+ .append($('<span>').html('Line'))
146
+ .attr('data', 'line')
147
+ .click(change_style)
148
+ )
149
+ )
150
+ .append(
151
+ $('<div class="btn_group mr"></div>')
152
+ .append(
153
+ $('<div></div>')
154
+ .addClass('button mr')
155
+ .addClass(opts.default_cardinal ? '' : 'active')
156
+ .append($('<span>').html('Off'))
157
+ .attr('data', 'cardinal-off')
158
+ .click(toggle_cardinal)
159
+ )
160
+ .append(
161
+ $('<div></div>')
162
+ .addClass('button ml')
163
+ .addClass(opts.default_cardinal ? 'active' : '')
164
+ .append($('<span>').html('On'))
165
+ .attr('data', 'cardinal-on')
166
+ .click(toggle_cardinal)
167
+ )
168
+ )
169
+ )
170
+ }
171
+
172
+ $(opts.elem)
173
+ .append(
174
+ $('<div></div>')
175
+ .addClass('legend')
176
+ .css({
177
+ margin: '10px 30px 0 30px',
178
+ })
179
+ )
180
+ .append(
181
+ $('<div></div>')
182
+ .addClass('container')
183
+ .css({
184
+ height: opts.height,
185
+ margin: '0 23px 25px 23px',
186
+ })
187
+ );
188
+
189
+ if(opts.async_chart){
190
+ $('.headbar', opts.elem).prepend(
191
+ $('<div></div>')
192
+ .addClass('button ml')
193
+ .append($('<span></span>').html('&larr;'))
194
+ .click(function(){ moveRange(-1); })
195
+ ).prepend(
196
+ $('<div></div>')
197
+ .addClass('datepicker')
198
+ ).prepend(
199
+ $('<div></div>')
200
+ .addClass('button')
201
+ .append($('<span></span>').html('&rarr;'))
202
+ .click(function(){ moveRange(1); })
203
+ ).prepend(
204
+ $('<div class="refresh_btn"></div>')
205
+ .addClass('button ml')
206
+ .append($('<span></span>').html('refresh'))
207
+ .click(function(){ requestValuesAsync(); })
208
+ );
209
+ }
210
+
211
+ if(opts.series_resolutions){
212
+ $('.headbar', opts.elem).append('<div class="tick_btns btn_group"></div>');
213
+ for(ind in opts.series_resolutions){
214
+ var _tick = opts.series_resolutions[ind];
215
+ $('.tick_btns', opts.elem).append(
216
+ $('<div></div>').attr('class', 'button tick').append($('<span></span>')
217
+ .html(FnordMetric.util.formatTimeRange(_tick)))
218
+ .attr('data-tick', _tick)
219
+ .click(function(){
220
+ resolution = $(this).attr('data-tick');
221
+ renderChart();
222
+ })
223
+ );
224
+ }
225
+ }
226
+
227
+ if((opts.autoupdate) && (opts.async_chart)){
228
+ var secs = parseInt(opts.autoupdate);
229
+ if(secs > 0){
230
+ var autoupdate_interval = window.setInterval(function(){
231
+ updateChart(false, true);
232
+ }, secs*1000);
233
+
234
+ $('body').bind('fm_dashboard_close', function(){
235
+ window.clearInterval(autoupdate_interval);
236
+ });
237
+ }
238
+ }
239
+ }
240
+
241
+ function renderChart(){
242
+ width = opts.elem.width() - 50;
243
+ height = opts.height || 240;
244
+
245
+ gconfig.width = width;
246
+ gconfig.height = height;
247
+
248
+ $(gconfig.element).html("");
249
+ $(".rickshaw_legend", opts.elem).html("");
250
+
251
+ if(opts.series_resolutions){
252
+ apply_resolution();
253
+ }
254
+
255
+ graph = new Rickshaw.Graph(gconfig);
256
+
257
+ legend = new Rickshaw.Graph.Legend({
258
+ graph: graph,
259
+ element: $('.legend', opts.elem)[0]
260
+ });
261
+
262
+ hoverDetail = new Rickshaw.Graph.HoverDetail( {
263
+ graph: graph
264
+ });
265
+
266
+ shelving = new Rickshaw.Graph.Behavior.Series.Toggle({
267
+ graph: graph,
268
+ legend: legend
269
+ });
270
+
271
+ highlighter = new Rickshaw.Graph.Behavior.Series.Highlight({
272
+ graph: graph,
273
+ legend: legend
274
+ });
275
+
276
+ new Rickshaw.Graph.Axis.Time({
277
+ graph: graph,
278
+ }).render();
279
+
280
+ new Rickshaw.Graph.Axis.Y({
281
+ graph: graph,
282
+ }).render();
283
+
284
+ if(!gconfig.renderer){
285
+ gconfig.renderer = "line";
286
+ }
287
+
288
+ graph.configure(gconfig);
289
+ graph.render();
290
+ }
291
+
292
+
293
+ function announce(evt){
294
+ if(evt.widget_key == opts.widget_key){
295
+ if((evt.class == "widget_response") && (evt.cmd == "values_at")){
296
+ running_request = false;
297
+ $(opts.elem).css('opacity', 1);
298
+ updateSeriesData(evt.gauges);
299
+ }
300
+ }
301
+ }
302
+
303
+ function requestValuesAsync(){
304
+ FnordMetric.publish({
305
+ "type": "widget_request",
306
+ "klass": "TimeseriesWidget",
307
+ "gauges": opts.gauges,
308
+ "cmd": "values_at",
309
+ "tick": opts.tick,
310
+ "since": opts.start_timestamp,
311
+ "until": opts.end_timestamp,
312
+ "widget_key": opts.widget_key
313
+ })
314
+ }
315
+
316
+ function updateSeriesData(dgauges){
317
+ gconfig.series = [];
318
+
319
+ for(var ind = 0; ind < dgauges.length; ind++){
320
+
321
+ gconfig.series.push({
322
+ name: dgauges[ind].key,
323
+ color: opts.series[ind].color,
324
+ data: []
325
+ });
326
+
327
+ for(_time in dgauges[ind].vals){
328
+ gconfig.series[ind].data.push(
329
+ { x: parseInt(_time), y: parseInt(dgauges[ind].vals[_time] || 0) }
330
+ );
331
+ }
332
+
333
+ }
334
+
335
+ renderChart();
336
+ }
337
+
338
+ function updateChart(first_time, silent){
339
+ if(!silent){ $(opts.elem).css('opacity', 0.5); }
340
+ updateRange();
341
+ redrawDatepicker();
342
+ requestValuesAsync();
343
+ }
344
+
345
+ function redrawDatepicker(){
346
+ $('.datepicker', opts.elem).html(
347
+ FnordMetric.util.dateFormat(opts.start_timestamp) +
348
+ '&nbsp;&dash;&nbsp;' +
349
+ FnordMetric.util.dateFormat(opts.end_timestamp)
350
+ );
351
+ }
352
+
353
+ function updateRange(force){
354
+ if(!opts.tick){
355
+ opts.tick = opts.ticks[0];
356
+ }
357
+
358
+ var _now = parseInt(new Date().getTime() / 1000);
359
+
360
+ if((opts.autoupdate) &&
361
+ ((_now - opts.end_timestamp) < ((opts.tick*(opts.autoupdate+1)) + 5))){
362
+ force = true;
363
+ }
364
+
365
+ if(!opts.start_timestamp || !opts.end_timestamp || !!force){
366
+ opts.end_timestamp = _now + opts.tick;
367
+ opts.start_timestamp = opts.end_timestamp - (opts.tick * xticks);
368
+ }
369
+ }
370
+
371
+ function moveRange(direction){
372
+ v = opts.tick*direction*8;
373
+
374
+ if(((opts.end_timestamp + v)*1000) < new Date().getTime()){
375
+ opts.start_timestamp += v;
376
+ opts.end_timestamp += v;
377
+ }
378
+
379
+ redrawDatepicker();
380
+ requestValuesAsync();
381
+ }
382
+
383
+ return {
384
+ render: render,
385
+ announce: announce
386
+ }
387
+
388
+ };
@@ -0,0 +1,112 @@
1
+ FnordMetric.widgets.toplistWidget = function(){
2
+
3
+ var opts;
4
+ var current_gauge = false;
5
+
6
+ function render(_opts){
7
+ opts = _opts;
8
+
9
+ var headbar = $('<div class="headbar"></div>').append(
10
+ $('<h2></h2>').html(opts.title)
11
+ );
12
+
13
+ opts.elem.append(headbar).css({
14
+ 'marginBottom': 20,
15
+ 'overflow': 'hidden'
16
+ }).append(
17
+ $('<div class="toplist_inner ui_toplist"></div>')
18
+ );
19
+
20
+ if(!opts.ticks){
21
+ var first = true;
22
+ for(k in opts.gauges){
23
+ headbar.append(
24
+ $('<div></div>')
25
+ .attr('class', 'button mr')
26
+ .attr('rel', k)
27
+ .append(
28
+ $('<span></span>').html(opts.gauges[k].title)
29
+ ).click(function(){
30
+ loadGauge($(this).attr('rel'));
31
+ }
32
+ )
33
+ );
34
+ if(first){
35
+ first = false;
36
+ loadGauge(k);
37
+ }
38
+ }
39
+ } else {
40
+ loadGauge(opts.gauges[0], true);
41
+ }
42
+
43
+ if(opts.autoupdate){
44
+ var secs = parseInt(opts.autoupdate);
45
+ if(secs > 0){
46
+
47
+ var autoupdate_interval = window.setInterval(function(){
48
+ loadGauge(false, true);
49
+ }, secs*1000);
50
+
51
+ $('body').bind('fm_dashboard_close', function(){
52
+ window.clearInterval(autoupdate_interval);
53
+ });
54
+ }
55
+ };
56
+
57
+ }
58
+
59
+ function loadGauge(gkey, silent){
60
+ if(!gkey)
61
+ gkey = current_gauge;
62
+
63
+ current_gauge = gkey;
64
+
65
+ if(!silent)
66
+ $('.toplist_inner', opts.elem).addClass('loading');
67
+
68
+ FnordMetric.publish({
69
+ "type": "widget_request",
70
+ "klass": "ToplistWidget",
71
+ "gauge": current_gauge,
72
+ "cmd": "values_for",
73
+ "tick": opts.tick,
74
+ "widget_key": opts.widget_key
75
+ });
76
+ }
77
+
78
+ function renderGauge(gkey, gdata){
79
+ var _elem = $('.toplist_inner', opts.elem).removeClass('loading').html('');
80
+ $(gdata.values).each(function(n, _gd){
81
+ var _perc = (parseInt(gdata.values[n][1]) / parseFloat(gdata.count))*100;
82
+ var _item = $('<div class="toplist_item"><div class="title"></div><div class="value"></div><div class="percent"></div></div>');
83
+
84
+ if(opts.click_callback){
85
+ var lelem = $('<a href="#" class="link">').html(gdata.values[n][0]);
86
+ var lclbck; eval("lclbck="+opts.click_callback)
87
+ lelem.attr('data-key', gdata.values[n][0]);
88
+ lelem.click(function(){ lclbck($(this).attr('data-key')); return false; });
89
+ $('.title', _item).html(lelem)
90
+ } else {
91
+ $('.title', _item).html(gdata.values[n][0]);
92
+ }
93
+
94
+ $('.value', _item).html(FnordMetric.util.formatGaugeValue(gkey, parseInt(gdata.values[n][1])));
95
+ $('.percent', _item).html(_perc.toFixed(1) + '%');
96
+ _elem.append(_item);
97
+ });
98
+ }
99
+
100
+ function announce(evt){
101
+
102
+ if((evt.widget_key == opts.widget_key) && ((evt.class == "widget_response") && (evt.cmd == "values_for")))
103
+ renderGauge(evt.gauge, evt);
104
+
105
+ }
106
+
107
+ return {
108
+ render: render,
109
+ announce: announce
110
+ };
111
+
112
+ };
@@ -0,0 +1,91 @@
1
+ FnordMetric.ui.navbar = function(elem, opts){
2
+
3
+ elem.css('width', $('#viewport').width());
4
+
5
+ var back = $('<a>')
6
+ .addClass('button back')
7
+ .html('&laquo;');
8
+
9
+ elem.prepend(back);
10
+
11
+ var head = $('<h1></h1>');
12
+ for(ind in opts.breadcrumb){
13
+ var cur = $('<a>').html(opts.breadcrumb[ind][0]);
14
+ if (ind == (opts.breadcrumb.length - 1)){
15
+ cur.addClass('current');
16
+ } else {
17
+ cur.append(' / ');
18
+ }
19
+ head.append(cur);
20
+ };
21
+
22
+ for(ind in opts.buttons){
23
+ var btn = $('<a>')
24
+ .addClass('button navbutton')
25
+ .click(opts.buttons[ind][1])
26
+ .html(opts.buttons[ind][0]);
27
+ elem.prepend(btn);
28
+ }
29
+
30
+ if(opts.datepicker){
31
+ elem.append(
32
+ $('<a class="button datepicker">')
33
+ .html('<div class="date">&nbsp;</div><i class="icon-calendar"></i>'));
34
+ }
35
+
36
+ elem.append(head);
37
+ }
38
+
39
+ FnordMetric.ui.modal = function(opts){
40
+ FnordMetric.ui.close_modal('body');
41
+ width = $("#viewport").width() * 0.8;
42
+
43
+ if (opts.max_width)
44
+ width = Math.min(opts.max_width, width);
45
+
46
+ var dialog_elem = $('<div class="modal">')
47
+ .append($('<div class="modal_inner">'))
48
+ .css({
49
+ "width": width,
50
+ "height": opts.height,
51
+ "marginLeft": (($("#viewport").width() - width) / 2),
52
+ "display": "none"
53
+ });
54
+
55
+
56
+ if (opts.max_width)
57
+ dialog_elem.css('maxWidth', opts.max_width);
58
+
59
+ if (opts.content)
60
+ dialog_elem.html(opts.content);
61
+
62
+ $("#viewport").append(dialog_elem);
63
+ dialog_elem.show().addClass('visible');
64
+ $('.modal_backdrop').show().addClass('visible');
65
+ }
66
+
67
+ FnordMetric.ui.close_modal = function(elem){
68
+ var m = $('.modal', $(elem));
69
+ m.push($(elem).closest('.modal'));
70
+
71
+ m.removeClass('visible');
72
+
73
+ window.setTimeout(function(){
74
+ m.remove();
75
+ }, 500);
76
+
77
+ $('.modal_backdrop').removeClass('visible').fadeOut();
78
+ }
79
+
80
+ FnordMetric.ui.resizable = function(elem){
81
+ $('.resizable', $(elem)).each(function(){
82
+ var wwperc = parseInt($(this).attr('data-width'));
83
+ if (!wwperc || (wwperc < 1)){ wwperc = 100; }
84
+ var wwidth = $('.viewport_inner').width() * (wwperc/100.0);
85
+ if(wwperc==100){ $(this).addClass('full_width'); }
86
+ else if($(this).hasClass('right')) { $(this).css('float', 'right'); }
87
+ else { wwidth -= 1; $(this).css('float', 'left'); }
88
+ $(this).css('overflow', 'hidden');
89
+ $(this).width(wwidth);
90
+ });
91
+ }