pulse-meter 0.2.8 → 0.2.9
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/pulse-meter/version.rb +1 -1
- data/lib/pulse-meter/visualize/app.rb +14 -0
- data/lib/pulse-meter/visualize/layout.rb +37 -0
- data/lib/pulse-meter/visualize/public/css/application.css +10 -4
- data/lib/pulse-meter/visualize/public/js/application.coffee +236 -34
- data/lib/pulse-meter/visualize/public/js/application.js +233 -25
- data/lib/pulse-meter/visualize/views/main.haml +4 -2
- data/lib/pulse-meter/visualize/views/sensors.haml +66 -0
- metadata +4 -4
- data/lib/pulse-meter/visualize/dsl/widget_old.rb +0 -95
data/lib/pulse-meter/version.rb
CHANGED
@@ -43,6 +43,20 @@ module PulseMeter
|
|
43
43
|
content_type :json
|
44
44
|
camelize_keys(@layout.widget(page_id - 1, id - 1, timespan: timespan)).to_json
|
45
45
|
end
|
46
|
+
|
47
|
+
get '/sensors' do
|
48
|
+
content_type :json
|
49
|
+
camelize_keys(@layout.sensor_list).to_json
|
50
|
+
end
|
51
|
+
|
52
|
+
get '/dynamic_widget' do
|
53
|
+
content_type :json
|
54
|
+
camelize_keys(@layout.dynamic_widget(
|
55
|
+
timespan: params[:timespan],
|
56
|
+
sensors: params[:sensor],
|
57
|
+
type: params[:type])
|
58
|
+
).to_json
|
59
|
+
end
|
46
60
|
end
|
47
61
|
end
|
48
62
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module PulseMeter
|
2
2
|
module Visualize
|
3
3
|
class Layout < Base
|
4
|
+
include PulseMeter::Mixins::Utils
|
5
|
+
|
4
6
|
def initialize(opts)
|
5
7
|
super
|
6
8
|
@opts[:pages] ||= []
|
@@ -37,6 +39,41 @@ module PulseMeter
|
|
37
39
|
def widgets(page_id)
|
38
40
|
pages[page_id].widget_datas
|
39
41
|
end
|
42
|
+
|
43
|
+
def sensor_list
|
44
|
+
PulseMeter::Sensor::Base
|
45
|
+
.list_objects
|
46
|
+
.select{|s| s.is_a?(PulseMeter::Sensor::Timeline)}
|
47
|
+
.map do |s|
|
48
|
+
{
|
49
|
+
id: s.name,
|
50
|
+
annotation: s.annotation,
|
51
|
+
type: s.class.to_s.split('::').last,
|
52
|
+
interval: s.interval
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
DEFAULT_TIMESPAN_IN_INTERVALS = 50
|
58
|
+
|
59
|
+
def dynamic_widget(args)
|
60
|
+
sensor_names = args[:sensors]
|
61
|
+
sensors = sensor_names.map{|n| PulseMeter::Sensor::Base.restore(n)}
|
62
|
+
timespan = if args[:timespan] && !args[:timespan].empty?
|
63
|
+
args[:timespan].to_i
|
64
|
+
else
|
65
|
+
sensors.first.interval * DEFAULT_TIMESPAN_IN_INTERVALS
|
66
|
+
end
|
67
|
+
|
68
|
+
type = args[:type]
|
69
|
+
widget_dsl_class = constantize("PulseMeter::Visualize::DSL::Widgets::#{type.capitalize}")
|
70
|
+
widget = widget_dsl_class.new('Dynamic Widget')
|
71
|
+
widget.timespan(timespan)
|
72
|
+
sensor_names.each{|n| widget.sensor(n)}
|
73
|
+
|
74
|
+
widget.to_data.data(id: 1)
|
75
|
+
end
|
76
|
+
|
40
77
|
end
|
41
78
|
end
|
42
79
|
end
|
@@ -10,16 +10,22 @@
|
|
10
10
|
margin-bottom: 50px;
|
11
11
|
}
|
12
12
|
|
13
|
+
#dynamic-chart-controls{
|
14
|
+
margin-top: 20px;
|
15
|
+
}
|
16
|
+
|
13
17
|
#chart-controls input{
|
14
|
-
|
18
|
+
width: 30px;
|
15
19
|
}
|
16
20
|
|
17
21
|
#chart-controls .space{
|
18
|
-
|
22
|
+
padding-left: 10px;
|
19
23
|
}
|
20
24
|
|
21
25
|
.footer{
|
22
|
-
|
23
|
-
|
26
|
+
margin-top: 100px;
|
27
|
+
text-align: center;
|
24
28
|
}
|
25
29
|
|
30
|
+
#dynamic-plotarea {
|
31
|
+
}
|
@@ -2,10 +2,31 @@ document.startApp = ->
|
|
2
2
|
globalOptions = gon.options
|
3
3
|
|
4
4
|
String::capitalize = ->
|
5
|
-
|
5
|
+
@charAt(0).toUpperCase() + @slice(1)
|
6
6
|
String::strip = ->
|
7
7
|
if String::trim? then @trim() else @replace /^\s+|\s+$/g, ""
|
8
8
|
|
9
|
+
Number::humanize = ->
|
10
|
+
interval = this
|
11
|
+
res = ""
|
12
|
+
s = interval % 60
|
13
|
+
res = "#{s} s" if s > 0
|
14
|
+
interval = (interval - s) / 60
|
15
|
+
return res unless interval > 0
|
16
|
+
|
17
|
+
m = interval % 60
|
18
|
+
res = "#{m} m #{res}".strip() if m > 0
|
19
|
+
interval = (interval - m) / 60
|
20
|
+
return res unless interval > 0
|
21
|
+
|
22
|
+
h = interval % 24
|
23
|
+
res = "#{h} h #{res}".strip() if h > 0
|
24
|
+
d = (interval - h) / 24
|
25
|
+
if d > 0
|
26
|
+
"#{d} d #{res}".strip()
|
27
|
+
else
|
28
|
+
res
|
29
|
+
|
9
30
|
PageInfo = Backbone.Model.extend {
|
10
31
|
}
|
11
32
|
|
@@ -14,9 +35,13 @@ document.startApp = ->
|
|
14
35
|
selected: ->
|
15
36
|
@find (m) ->
|
16
37
|
m.get 'selected'
|
17
|
-
|
38
|
+
|
18
39
|
selectFirst: ->
|
19
40
|
@at(0).set('selected', true) if @length > 0
|
41
|
+
|
42
|
+
selectNone: ->
|
43
|
+
@each (m) ->
|
44
|
+
m.set 'selected', false
|
20
45
|
|
21
46
|
selectPage: (id) ->
|
22
47
|
@each (m) ->
|
@@ -83,12 +108,18 @@ document.startApp = ->
|
|
83
108
|
height: 300
|
84
109
|
}
|
85
110
|
|
86
|
-
mergedOptions: ->
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
111
|
+
mergedOptions: ->
|
112
|
+
pageOptions = if pageInfos.selected()
|
113
|
+
pageInfos.selected().get('gchartOptions')
|
114
|
+
else
|
115
|
+
{}
|
116
|
+
|
117
|
+
$.extend(true,
|
118
|
+
@options(),
|
119
|
+
globalOptions.gchartOptions,
|
120
|
+
pageOptions,
|
121
|
+
@get('gchartOptions')
|
122
|
+
)
|
92
123
|
|
93
124
|
data: -> new google.visualization.DataTable
|
94
125
|
|
@@ -171,7 +202,7 @@ document.startApp = ->
|
|
171
202
|
position: 'bottom'
|
172
203
|
}
|
173
204
|
vAxis: {
|
174
|
-
title:
|
205
|
+
title: @valuesTitle()
|
175
206
|
}
|
176
207
|
hAxis: {
|
177
208
|
format: format
|
@@ -180,27 +211,15 @@ document.startApp = ->
|
|
180
211
|
axisTitlesPosition: 'in'
|
181
212
|
}
|
182
213
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
s = interval % 60
|
187
|
-
res = "#{s} s" if s > 0
|
188
|
-
interval = (interval - s) / 60
|
189
|
-
return res unless interval > 0
|
190
|
-
|
191
|
-
m = interval % 60
|
192
|
-
res = "#{m} m #{res}".strip() if m > 0
|
193
|
-
interval = (interval - m) / 60
|
194
|
-
return res unless interval > 0
|
195
|
-
|
196
|
-
h = interval % 24
|
197
|
-
res = "#{h} h #{res}".strip() if h > 0
|
198
|
-
d = (interval - h) / 24
|
199
|
-
if d > 0
|
200
|
-
"#{d} d #{res}".strip()
|
214
|
+
valuesTitle: ->
|
215
|
+
if @get('valuesTitle')
|
216
|
+
"#{@get('valuesTitle')} / #{@humanizedInterval()}"
|
201
217
|
else
|
202
|
-
|
218
|
+
@humanizedInterval()
|
219
|
+
|
203
220
|
|
221
|
+
humanizedInterval: ->
|
222
|
+
@get('interval').humanize()
|
204
223
|
|
205
224
|
cutoff: (min, max) ->
|
206
225
|
_.each(@get('series').rows, (row) ->
|
@@ -288,12 +307,191 @@ document.startApp = ->
|
|
288
307
|
|
289
308
|
}
|
290
309
|
|
310
|
+
DynamicWidget = Backbone.Model.extend {
|
311
|
+
|
312
|
+
increaseTimespan: (inc) ->
|
313
|
+
@set('timespan', @timespan() + inc)
|
314
|
+
|
315
|
+
resetTimespan: ->
|
316
|
+
@set('timespan', null)
|
317
|
+
|
318
|
+
timespan: -> @get('timespan')
|
319
|
+
|
320
|
+
sensorArgs: ->
|
321
|
+
_.map(@get('sensorIds'), (name) -> "sensor[]=#{name}").join('&')
|
322
|
+
|
323
|
+
url: ->
|
324
|
+
url = "#{ROOT}dynamic_widget?#{@sensorArgs()}&type=#{@get('type')}"
|
325
|
+
|
326
|
+
timespan = @timespan()
|
327
|
+
url = "#{url}×pan=#{timespan}" if timespan? && !_.isNaN(timespan)
|
328
|
+
|
329
|
+
url
|
330
|
+
|
331
|
+
forceUpdate: ->
|
332
|
+
@fetch {
|
333
|
+
success: (model, response) ->
|
334
|
+
model.trigger('redraw')
|
335
|
+
}
|
336
|
+
}
|
337
|
+
|
291
338
|
WidgetList = Backbone.Collection.extend {
|
292
339
|
model: Widget
|
293
340
|
url: ->
|
294
341
|
ROOT + 'pages/' + pageInfos.selected().id + '/widgets'
|
295
342
|
}
|
296
343
|
|
344
|
+
SensorInfo = Backbone.Model.extend {
|
345
|
+
|
346
|
+
}
|
347
|
+
|
348
|
+
SensorInfoList = Backbone.Collection.extend {
|
349
|
+
model: SensorInfo
|
350
|
+
url: ->
|
351
|
+
ROOT + 'sensors'
|
352
|
+
}
|
353
|
+
|
354
|
+
|
355
|
+
SensorInfoListView = Backbone.View.extend {
|
356
|
+
tagName: 'div'
|
357
|
+
|
358
|
+
template: ->
|
359
|
+
_.template($("#sensor-list").html())
|
360
|
+
|
361
|
+
initialize: (sensorInfo) ->
|
362
|
+
@sensorInfo = sensorInfo
|
363
|
+
@sensorInfo.bind 'reset', @render, this
|
364
|
+
|
365
|
+
render: ->
|
366
|
+
@$el.html @template()({sensors: @sensorInfo.toJSON()})
|
367
|
+
|
368
|
+
selectedSensors: ->
|
369
|
+
checked = _.filter @$el.find('.sensor-box'), (el) -> $(el).is(':checked')
|
370
|
+
ids = {}
|
371
|
+
_.each checked, (box) -> ids[box.id] = true
|
372
|
+
selected = @sensorInfo.filter (sensor) -> ids[sensor.id]
|
373
|
+
|
374
|
+
}
|
375
|
+
|
376
|
+
DynamicChartView = Backbone.View.extend {
|
377
|
+
initialize: ->
|
378
|
+
@sensors = []
|
379
|
+
@type = 'Area'
|
380
|
+
@widget = new DynamicWidget
|
381
|
+
|
382
|
+
@widget.bind('destroy', @remove, this)
|
383
|
+
@widget.bind('redraw', @redrawChart, this)
|
384
|
+
|
385
|
+
tagName: 'div'
|
386
|
+
|
387
|
+
events: {
|
388
|
+
"click #refresh-chart": 'update'
|
389
|
+
"click #extend-timespan": 'extendTimespan'
|
390
|
+
"click #reset-timespan": 'resetTimespan'
|
391
|
+
}
|
392
|
+
|
393
|
+
template: -> _.template($("#dynamic-widget-plotarea").html())
|
394
|
+
|
395
|
+
render: ->
|
396
|
+
@$el.html(@template()())
|
397
|
+
|
398
|
+
extendTimespan: ->
|
399
|
+
select = @$el.find("#extend-timespan-val")
|
400
|
+
val = select.first().val()
|
401
|
+
@widget.increaseTimespan(parseInt(val))
|
402
|
+
@update()
|
403
|
+
|
404
|
+
resetTimespan: ->
|
405
|
+
@widget.resetTimespan()
|
406
|
+
@update()
|
407
|
+
|
408
|
+
sensorIds: -> _.map(@sensors, (s) -> s.id)
|
409
|
+
|
410
|
+
redrawChart: ->
|
411
|
+
if @presenter
|
412
|
+
@presenter.draw()
|
413
|
+
else
|
414
|
+
@presenter = WidgetPresenter.create(@widget, @chartContainer())
|
415
|
+
|
416
|
+
|
417
|
+
chartContainer: ->
|
418
|
+
@$el.find('#chart')[0]
|
419
|
+
|
420
|
+
update: ->
|
421
|
+
@widget.forceUpdate() if @sensors.length > 0
|
422
|
+
|
423
|
+
draw: (sensors, type) ->
|
424
|
+
@sensors = sensors
|
425
|
+
@type = type
|
426
|
+
|
427
|
+
@widget.set('sensorIds', @sensorIds())
|
428
|
+
@widget.set('type', @type)
|
429
|
+
|
430
|
+
@presenter = null
|
431
|
+
$(@chartContainer()).empty()
|
432
|
+
@widget.forceUpdate()
|
433
|
+
}
|
434
|
+
|
435
|
+
DynamicWidgetView = Backbone.View.extend {
|
436
|
+
tagName: 'div'
|
437
|
+
|
438
|
+
initialize: ->
|
439
|
+
@sensorInfo = new SensorInfoList
|
440
|
+
|
441
|
+
@sensorListView = new SensorInfoListView(@sensorInfo)
|
442
|
+
@chartView = new DynamicChartView
|
443
|
+
|
444
|
+
@$el.html(@template()())
|
445
|
+
|
446
|
+
@$el.find('#sensor-list-area').append(@sensorListView.el)
|
447
|
+
|
448
|
+
@chartView.render()
|
449
|
+
@$el.find('#dynamic-plotarea').append(@chartView.el)
|
450
|
+
|
451
|
+
events: {
|
452
|
+
"click #sensor-controls #refresh": 'refresh'
|
453
|
+
"click #sensor-controls #draw": 'drawChart'
|
454
|
+
}
|
455
|
+
|
456
|
+
template: ->
|
457
|
+
_.template($("#dynamic-widget").html())
|
458
|
+
|
459
|
+
errorTemplate: -> _.template($("#dynamic-widget-error").html())
|
460
|
+
|
461
|
+
error: (error)->
|
462
|
+
@$el.find('#errors').append(@errorTemplate()(error: error))
|
463
|
+
|
464
|
+
|
465
|
+
refresh: ->
|
466
|
+
@sensorInfo.fetch()
|
467
|
+
|
468
|
+
intervalsEqual: (sensors) ->
|
469
|
+
interval = sensors[0].get('interval')
|
470
|
+
badIntervals = _.filter(sensors, (s) ->
|
471
|
+
s.get('interval') != interval
|
472
|
+
)
|
473
|
+
badIntervals.length == 0
|
474
|
+
|
475
|
+
drawChart: ->
|
476
|
+
selectedSensors = @sensorListView.selectedSensors()
|
477
|
+
return unless selectedSensors.length > 0
|
478
|
+
|
479
|
+
unless @intervalsEqual(selectedSensors)
|
480
|
+
@error('Selected sensors have different intervals')
|
481
|
+
return
|
482
|
+
|
483
|
+
type = @$el.find('#chart-type').val()
|
484
|
+
@chartView.draw(selectedSensors, type)
|
485
|
+
|
486
|
+
render: (container) ->
|
487
|
+
container.empty()
|
488
|
+
container.append(@$el)
|
489
|
+
@sensorInfo.fetch()
|
490
|
+
@chartView.update()
|
491
|
+
|
492
|
+
}
|
493
|
+
|
494
|
+
|
297
495
|
WidgetChartView = Backbone.View.extend {
|
298
496
|
tagName: 'div'
|
299
497
|
|
@@ -370,8 +568,9 @@ document.startApp = ->
|
|
370
568
|
|
371
569
|
widgetList = new WidgetList
|
372
570
|
setInterval( ->
|
373
|
-
|
374
|
-
|
571
|
+
if pageInfos.selected()
|
572
|
+
widgetList.each (w) ->
|
573
|
+
w.refetch()
|
375
574
|
, 200)
|
376
575
|
|
377
576
|
WidgetListView = Backbone.View.extend {
|
@@ -382,9 +581,7 @@ document.startApp = ->
|
|
382
581
|
container = $('#widgets')
|
383
582
|
container.empty()
|
384
583
|
widgetList.each (w) ->
|
385
|
-
view = new WidgetView {
|
386
|
-
model: w
|
387
|
-
}
|
584
|
+
view = new WidgetView { model: w }
|
388
585
|
view.render()
|
389
586
|
container.append(view.el)
|
390
587
|
view.renderChart()
|
@@ -395,14 +592,19 @@ document.startApp = ->
|
|
395
592
|
AppRouter = Backbone.Router.extend {
|
396
593
|
routes: {
|
397
594
|
'pages/:id': 'getPage'
|
595
|
+
'custom': 'custom'
|
398
596
|
'*actions': 'defaultRoute'
|
399
597
|
}
|
400
598
|
getPage: (ids) ->
|
401
599
|
id = parseInt(ids)
|
402
600
|
pageInfos.selectPage(id)
|
403
601
|
widgetList.fetch()
|
602
|
+
custom: ->
|
603
|
+
pageInfos.selectNone()
|
604
|
+
dynamicWidget = new DynamicWidgetView
|
605
|
+
dynamicWidget.render($('#widgets'))
|
404
606
|
defaultRoute: (actions) ->
|
405
|
-
@navigate('//
|
607
|
+
@navigate('//custom')
|
406
608
|
}
|
407
609
|
|
408
610
|
appRouter = new AppRouter
|
@@ -4,7 +4,7 @@
|
|
4
4
|
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };
|
5
5
|
|
6
6
|
document.startApp = function() {
|
7
|
-
var AppRouter, AreaPresenter, GaugePresenter, LinePresenter, PageInfo, PageInfoList, PageTitleView, PageTitlesView, PiePresenter, SeriesPresenter, TablePresenter, TimelinePresenter, Widget, WidgetChartView, WidgetList, WidgetListView, WidgetPresenter, WidgetView, appRouter, globalOptions, pageInfos, pageTitlesApp, widgetList, widgetListApp;
|
7
|
+
var AppRouter, AreaPresenter, DynamicChartView, DynamicWidget, DynamicWidgetView, GaugePresenter, LinePresenter, PageInfo, PageInfoList, PageTitleView, PageTitlesView, PiePresenter, SensorInfo, SensorInfoList, SensorInfoListView, SeriesPresenter, TablePresenter, TimelinePresenter, Widget, WidgetChartView, WidgetList, WidgetListView, WidgetPresenter, WidgetView, appRouter, globalOptions, pageInfos, pageTitlesApp, widgetList, widgetListApp;
|
8
8
|
globalOptions = gon.options;
|
9
9
|
String.prototype.capitalize = function() {
|
10
10
|
return this.charAt(0).toUpperCase() + this.slice(1);
|
@@ -16,6 +16,27 @@
|
|
16
16
|
return this.replace(/^\s+|\s+$/g, "");
|
17
17
|
}
|
18
18
|
};
|
19
|
+
Number.prototype.humanize = function() {
|
20
|
+
var d, h, interval, m, res, s;
|
21
|
+
interval = this;
|
22
|
+
res = "";
|
23
|
+
s = interval % 60;
|
24
|
+
if (s > 0) res = "" + s + " s";
|
25
|
+
interval = (interval - s) / 60;
|
26
|
+
if (!(interval > 0)) return res;
|
27
|
+
m = interval % 60;
|
28
|
+
if (m > 0) res = ("" + m + " m " + res).strip();
|
29
|
+
interval = (interval - m) / 60;
|
30
|
+
if (!(interval > 0)) return res;
|
31
|
+
h = interval % 24;
|
32
|
+
if (h > 0) res = ("" + h + " h " + res).strip();
|
33
|
+
d = (interval - h) / 24;
|
34
|
+
if (d > 0) {
|
35
|
+
return ("" + d + " d " + res).strip();
|
36
|
+
} else {
|
37
|
+
return res;
|
38
|
+
}
|
39
|
+
};
|
19
40
|
PageInfo = Backbone.Model.extend({});
|
20
41
|
PageInfoList = Backbone.Collection.extend({
|
21
42
|
model: PageInfo,
|
@@ -27,6 +48,11 @@
|
|
27
48
|
selectFirst: function() {
|
28
49
|
if (this.length > 0) return this.at(0).set('selected', true);
|
29
50
|
},
|
51
|
+
selectNone: function() {
|
52
|
+
return this.each(function(m) {
|
53
|
+
return m.set('selected', false);
|
54
|
+
});
|
55
|
+
},
|
30
56
|
selectPage: function(id) {
|
31
57
|
return this.each(function(m) {
|
32
58
|
return m.set('selected', m.id === id);
|
@@ -101,7 +127,9 @@
|
|
101
127
|
};
|
102
128
|
|
103
129
|
WidgetPresenter.prototype.mergedOptions = function() {
|
104
|
-
|
130
|
+
var pageOptions;
|
131
|
+
pageOptions = pageInfos.selected() ? pageInfos.selected().get('gchartOptions') : {};
|
132
|
+
return $.extend(true, this.options(), globalOptions.gchartOptions, pageOptions, this.get('gchartOptions'));
|
105
133
|
};
|
106
134
|
|
107
135
|
WidgetPresenter.prototype.data = function() {
|
@@ -234,7 +262,7 @@
|
|
234
262
|
position: 'bottom'
|
235
263
|
},
|
236
264
|
vAxis: {
|
237
|
-
title:
|
265
|
+
title: this.valuesTitle()
|
238
266
|
},
|
239
267
|
hAxis: {
|
240
268
|
format: format
|
@@ -244,28 +272,18 @@
|
|
244
272
|
});
|
245
273
|
};
|
246
274
|
|
247
|
-
SeriesPresenter.prototype.
|
248
|
-
|
249
|
-
|
250
|
-
res = "";
|
251
|
-
s = interval % 60;
|
252
|
-
if (s > 0) res = "" + s + " s";
|
253
|
-
interval = (interval - s) / 60;
|
254
|
-
if (!(interval > 0)) return res;
|
255
|
-
m = interval % 60;
|
256
|
-
if (m > 0) res = ("" + m + " m " + res).strip();
|
257
|
-
interval = (interval - m) / 60;
|
258
|
-
if (!(interval > 0)) return res;
|
259
|
-
h = interval % 24;
|
260
|
-
if (h > 0) res = ("" + h + " h " + res).strip();
|
261
|
-
d = (interval - h) / 24;
|
262
|
-
if (d > 0) {
|
263
|
-
return ("" + d + " d " + res).strip();
|
275
|
+
SeriesPresenter.prototype.valuesTitle = function() {
|
276
|
+
if (this.get('valuesTitle')) {
|
277
|
+
return "" + (this.get('valuesTitle')) + " / " + (this.humanizedInterval());
|
264
278
|
} else {
|
265
|
-
return
|
279
|
+
return this.humanizedInterval();
|
266
280
|
}
|
267
281
|
};
|
268
282
|
|
283
|
+
SeriesPresenter.prototype.humanizedInterval = function() {
|
284
|
+
return this.get('interval').humanize();
|
285
|
+
};
|
286
|
+
|
269
287
|
SeriesPresenter.prototype.cutoff = function(min, max) {
|
270
288
|
return _.each(this.get('series').rows, function(row) {
|
271
289
|
var i, value, _i, _ref, _results;
|
@@ -416,12 +434,193 @@
|
|
416
434
|
});
|
417
435
|
}
|
418
436
|
});
|
437
|
+
DynamicWidget = Backbone.Model.extend({
|
438
|
+
increaseTimespan: function(inc) {
|
439
|
+
return this.set('timespan', this.timespan() + inc);
|
440
|
+
},
|
441
|
+
resetTimespan: function() {
|
442
|
+
return this.set('timespan', null);
|
443
|
+
},
|
444
|
+
timespan: function() {
|
445
|
+
return this.get('timespan');
|
446
|
+
},
|
447
|
+
sensorArgs: function() {
|
448
|
+
return _.map(this.get('sensorIds'), function(name) {
|
449
|
+
return "sensor[]=" + name;
|
450
|
+
}).join('&');
|
451
|
+
},
|
452
|
+
url: function() {
|
453
|
+
var timespan, url;
|
454
|
+
url = "" + ROOT + "dynamic_widget?" + (this.sensorArgs()) + "&type=" + (this.get('type'));
|
455
|
+
timespan = this.timespan();
|
456
|
+
if ((timespan != null) && !_.isNaN(timespan)) {
|
457
|
+
url = "" + url + "×pan=" + timespan;
|
458
|
+
}
|
459
|
+
return url;
|
460
|
+
},
|
461
|
+
forceUpdate: function() {
|
462
|
+
return this.fetch({
|
463
|
+
success: function(model, response) {
|
464
|
+
return model.trigger('redraw');
|
465
|
+
}
|
466
|
+
});
|
467
|
+
}
|
468
|
+
});
|
419
469
|
WidgetList = Backbone.Collection.extend({
|
420
470
|
model: Widget,
|
421
471
|
url: function() {
|
422
472
|
return ROOT + 'pages/' + pageInfos.selected().id + '/widgets';
|
423
473
|
}
|
424
474
|
});
|
475
|
+
SensorInfo = Backbone.Model.extend({});
|
476
|
+
SensorInfoList = Backbone.Collection.extend({
|
477
|
+
model: SensorInfo,
|
478
|
+
url: function() {
|
479
|
+
return ROOT + 'sensors';
|
480
|
+
}
|
481
|
+
});
|
482
|
+
SensorInfoListView = Backbone.View.extend({
|
483
|
+
tagName: 'div',
|
484
|
+
template: function() {
|
485
|
+
return _.template($("#sensor-list").html());
|
486
|
+
},
|
487
|
+
initialize: function(sensorInfo) {
|
488
|
+
this.sensorInfo = sensorInfo;
|
489
|
+
return this.sensorInfo.bind('reset', this.render, this);
|
490
|
+
},
|
491
|
+
render: function() {
|
492
|
+
return this.$el.html(this.template()({
|
493
|
+
sensors: this.sensorInfo.toJSON()
|
494
|
+
}));
|
495
|
+
},
|
496
|
+
selectedSensors: function() {
|
497
|
+
var checked, ids, selected;
|
498
|
+
checked = _.filter(this.$el.find('.sensor-box'), function(el) {
|
499
|
+
return $(el).is(':checked');
|
500
|
+
});
|
501
|
+
ids = {};
|
502
|
+
_.each(checked, function(box) {
|
503
|
+
return ids[box.id] = true;
|
504
|
+
});
|
505
|
+
return selected = this.sensorInfo.filter(function(sensor) {
|
506
|
+
return ids[sensor.id];
|
507
|
+
});
|
508
|
+
}
|
509
|
+
});
|
510
|
+
DynamicChartView = Backbone.View.extend({
|
511
|
+
initialize: function() {
|
512
|
+
this.sensors = [];
|
513
|
+
this.type = 'Area';
|
514
|
+
this.widget = new DynamicWidget;
|
515
|
+
this.widget.bind('destroy', this.remove, this);
|
516
|
+
return this.widget.bind('redraw', this.redrawChart, this);
|
517
|
+
},
|
518
|
+
tagName: 'div',
|
519
|
+
events: {
|
520
|
+
"click #refresh-chart": 'update',
|
521
|
+
"click #extend-timespan": 'extendTimespan',
|
522
|
+
"click #reset-timespan": 'resetTimespan'
|
523
|
+
},
|
524
|
+
template: function() {
|
525
|
+
return _.template($("#dynamic-widget-plotarea").html());
|
526
|
+
},
|
527
|
+
render: function() {
|
528
|
+
return this.$el.html(this.template()());
|
529
|
+
},
|
530
|
+
extendTimespan: function() {
|
531
|
+
var select, val;
|
532
|
+
select = this.$el.find("#extend-timespan-val");
|
533
|
+
val = select.first().val();
|
534
|
+
this.widget.increaseTimespan(parseInt(val));
|
535
|
+
return this.update();
|
536
|
+
},
|
537
|
+
resetTimespan: function() {
|
538
|
+
this.widget.resetTimespan();
|
539
|
+
return this.update();
|
540
|
+
},
|
541
|
+
sensorIds: function() {
|
542
|
+
return _.map(this.sensors, function(s) {
|
543
|
+
return s.id;
|
544
|
+
});
|
545
|
+
},
|
546
|
+
redrawChart: function() {
|
547
|
+
if (this.presenter) {
|
548
|
+
return this.presenter.draw();
|
549
|
+
} else {
|
550
|
+
return this.presenter = WidgetPresenter.create(this.widget, this.chartContainer());
|
551
|
+
}
|
552
|
+
},
|
553
|
+
chartContainer: function() {
|
554
|
+
return this.$el.find('#chart')[0];
|
555
|
+
},
|
556
|
+
update: function() {
|
557
|
+
if (this.sensors.length > 0) return this.widget.forceUpdate();
|
558
|
+
},
|
559
|
+
draw: function(sensors, type) {
|
560
|
+
this.sensors = sensors;
|
561
|
+
this.type = type;
|
562
|
+
this.widget.set('sensorIds', this.sensorIds());
|
563
|
+
this.widget.set('type', this.type);
|
564
|
+
this.presenter = null;
|
565
|
+
$(this.chartContainer()).empty();
|
566
|
+
return this.widget.forceUpdate();
|
567
|
+
}
|
568
|
+
});
|
569
|
+
DynamicWidgetView = Backbone.View.extend({
|
570
|
+
tagName: 'div',
|
571
|
+
initialize: function() {
|
572
|
+
this.sensorInfo = new SensorInfoList;
|
573
|
+
this.sensorListView = new SensorInfoListView(this.sensorInfo);
|
574
|
+
this.chartView = new DynamicChartView;
|
575
|
+
this.$el.html(this.template()());
|
576
|
+
this.$el.find('#sensor-list-area').append(this.sensorListView.el);
|
577
|
+
this.chartView.render();
|
578
|
+
return this.$el.find('#dynamic-plotarea').append(this.chartView.el);
|
579
|
+
},
|
580
|
+
events: {
|
581
|
+
"click #sensor-controls #refresh": 'refresh',
|
582
|
+
"click #sensor-controls #draw": 'drawChart'
|
583
|
+
},
|
584
|
+
template: function() {
|
585
|
+
return _.template($("#dynamic-widget").html());
|
586
|
+
},
|
587
|
+
errorTemplate: function() {
|
588
|
+
return _.template($("#dynamic-widget-error").html());
|
589
|
+
},
|
590
|
+
error: function(error) {
|
591
|
+
return this.$el.find('#errors').append(this.errorTemplate()({
|
592
|
+
error: error
|
593
|
+
}));
|
594
|
+
},
|
595
|
+
refresh: function() {
|
596
|
+
return this.sensorInfo.fetch();
|
597
|
+
},
|
598
|
+
intervalsEqual: function(sensors) {
|
599
|
+
var badIntervals, interval;
|
600
|
+
interval = sensors[0].get('interval');
|
601
|
+
badIntervals = _.filter(sensors, function(s) {
|
602
|
+
return s.get('interval') !== interval;
|
603
|
+
});
|
604
|
+
return badIntervals.length === 0;
|
605
|
+
},
|
606
|
+
drawChart: function() {
|
607
|
+
var selectedSensors, type;
|
608
|
+
selectedSensors = this.sensorListView.selectedSensors();
|
609
|
+
if (!(selectedSensors.length > 0)) return;
|
610
|
+
if (!this.intervalsEqual(selectedSensors)) {
|
611
|
+
this.error('Selected sensors have different intervals');
|
612
|
+
return;
|
613
|
+
}
|
614
|
+
type = this.$el.find('#chart-type').val();
|
615
|
+
return this.chartView.draw(selectedSensors, type);
|
616
|
+
},
|
617
|
+
render: function(container) {
|
618
|
+
container.empty();
|
619
|
+
container.append(this.$el);
|
620
|
+
this.sensorInfo.fetch();
|
621
|
+
return this.chartView.update();
|
622
|
+
}
|
623
|
+
});
|
425
624
|
WidgetChartView = Backbone.View.extend({
|
426
625
|
tagName: 'div',
|
427
626
|
initialize: function() {
|
@@ -506,9 +705,11 @@
|
|
506
705
|
});
|
507
706
|
widgetList = new WidgetList;
|
508
707
|
setInterval(function() {
|
509
|
-
|
510
|
-
return
|
511
|
-
|
708
|
+
if (pageInfos.selected()) {
|
709
|
+
return widgetList.each(function(w) {
|
710
|
+
return w.refetch();
|
711
|
+
});
|
712
|
+
}
|
512
713
|
}, 200);
|
513
714
|
WidgetListView = Backbone.View.extend({
|
514
715
|
initialize: function() {
|
@@ -533,6 +734,7 @@
|
|
533
734
|
AppRouter = Backbone.Router.extend({
|
534
735
|
routes: {
|
535
736
|
'pages/:id': 'getPage',
|
737
|
+
'custom': 'custom',
|
536
738
|
'*actions': 'defaultRoute'
|
537
739
|
},
|
538
740
|
getPage: function(ids) {
|
@@ -541,8 +743,14 @@
|
|
541
743
|
pageInfos.selectPage(id);
|
542
744
|
return widgetList.fetch();
|
543
745
|
},
|
746
|
+
custom: function() {
|
747
|
+
var dynamicWidget;
|
748
|
+
pageInfos.selectNone();
|
749
|
+
dynamicWidget = new DynamicWidgetView;
|
750
|
+
return dynamicWidget.render($('#widgets'));
|
751
|
+
},
|
544
752
|
defaultRoute: function(actions) {
|
545
|
-
|
753
|
+
return this.navigate('//custom');
|
546
754
|
}
|
547
755
|
});
|
548
756
|
appRouter = new AppRouter;
|
@@ -19,6 +19,7 @@
|
|
19
19
|
|
20
20
|
- %w(area line table pie gauge).each do |wtype|
|
21
21
|
= partial "widgets/#{wtype}"
|
22
|
+
= partial "sensors"
|
22
23
|
|
23
24
|
.container#main
|
24
25
|
.row
|
@@ -26,7 +27,8 @@
|
|
26
27
|
.navbar
|
27
28
|
.navbar-inner
|
28
29
|
.container
|
29
|
-
%
|
30
|
-
|
30
|
+
%a{href: '#/custom'}
|
31
|
+
%span.brand= @title
|
32
|
+
%ul.nav#page-titles
|
31
33
|
#widgets.row
|
32
34
|
|
@@ -0,0 +1,66 @@
|
|
1
|
+
%script#sensor-list{type: 'text/template'}
|
2
|
+
%table#sensors.table.table-striped
|
3
|
+
%tr
|
4
|
+
%th
|
5
|
+
%th Title
|
6
|
+
%th Type
|
7
|
+
%th Interval
|
8
|
+
<% _.each(sensors, function(sensor) { %>
|
9
|
+
%tr
|
10
|
+
%td
|
11
|
+
:plain
|
12
|
+
<input class="sensor-box" type="checkbox" id="<%- sensor.id %>" />
|
13
|
+
%td
|
14
|
+
<%- sensor.annotation %>
|
15
|
+
%sub <%- sensor.id %>
|
16
|
+
%td <%- sensor.type %>
|
17
|
+
%td <%- sensor.interval.humanize() %>
|
18
|
+
<% }) %>
|
19
|
+
|
20
|
+
%script#dynamic-widget-error{type: 'text/template'}
|
21
|
+
.alert.alert-error
|
22
|
+
.button.close{'data-dismiss' => 'alert'} ×
|
23
|
+
<%- error %>
|
24
|
+
|
25
|
+
%script#dynamic-widget-plotarea{type: 'text/template'}
|
26
|
+
.well
|
27
|
+
#chart
|
28
|
+
%h3 Choose sensors to plot
|
29
|
+
#dynamic-chart-controls
|
30
|
+
.form-inline
|
31
|
+
%button#refresh-chart.btn.btn-mini
|
32
|
+
%i.icon-refresh
|
33
|
+
Refresh chart
|
34
|
+
Timespan:
|
35
|
+
%select#extend-timespan-val.btn-mini.span1
|
36
|
+
= partial "widgets/extend_options"
|
37
|
+
%button#extend-timespan.btn.btn-mini
|
38
|
+
%i.icon-arrow-left
|
39
|
+
Extend
|
40
|
+
%button#reset-timespan.btn.btn-mini
|
41
|
+
%i.icon-arrow-right
|
42
|
+
Reset
|
43
|
+
|
44
|
+
|
45
|
+
%script#dynamic-widget{type: 'text/template'}
|
46
|
+
.span10
|
47
|
+
#dynamic-plotarea
|
48
|
+
#errors
|
49
|
+
.span10
|
50
|
+
#sensor-controls.form-horizontal
|
51
|
+
%p.form-inline
|
52
|
+
Chart type
|
53
|
+
%select#chart-type{value: 'area'}
|
54
|
+
%option{value: 'area'} Area
|
55
|
+
%option{value: 'line'} Line
|
56
|
+
%option{value: 'table'} Table
|
57
|
+
%button#draw.btn.btn-mini.btn-primary
|
58
|
+
%i.icon-play
|
59
|
+
Draw
|
60
|
+
%button#refresh.btn.btn-mini
|
61
|
+
%i.icon-refresh
|
62
|
+
Refresh sensor list
|
63
|
+
#sensor-list-area
|
64
|
+
|
65
|
+
|
66
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pulse-meter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.9
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-08-
|
13
|
+
date: 2012-08-03 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: gon-sinatra
|
@@ -370,7 +370,6 @@ files:
|
|
370
370
|
- lib/pulse-meter/visualize/dsl/page.rb
|
371
371
|
- lib/pulse-meter/visualize/dsl/sensor.rb
|
372
372
|
- lib/pulse-meter/visualize/dsl/widget.rb
|
373
|
-
- lib/pulse-meter/visualize/dsl/widget_old.rb
|
374
373
|
- lib/pulse-meter/visualize/dsl/widgets/area.rb
|
375
374
|
- lib/pulse-meter/visualize/dsl/widgets/gauge.rb
|
376
375
|
- lib/pulse-meter/visualize/dsl/widgets/line.rb
|
@@ -394,6 +393,7 @@ files:
|
|
394
393
|
- lib/pulse-meter/visualize/sensor.rb
|
395
394
|
- lib/pulse-meter/visualize/series_extractor.rb
|
396
395
|
- lib/pulse-meter/visualize/views/main.haml
|
396
|
+
- lib/pulse-meter/visualize/views/sensors.haml
|
397
397
|
- lib/pulse-meter/visualize/views/widgets/area.haml
|
398
398
|
- lib/pulse-meter/visualize/views/widgets/extend_options.haml
|
399
399
|
- lib/pulse-meter/visualize/views/widgets/gauge.haml
|
@@ -475,7 +475,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
475
475
|
version: '0'
|
476
476
|
segments:
|
477
477
|
- 0
|
478
|
-
hash: -
|
478
|
+
hash: -3651038348403284878
|
479
479
|
requirements: []
|
480
480
|
rubyforge_project:
|
481
481
|
rubygems_version: 1.8.24
|
@@ -1,95 +0,0 @@
|
|
1
|
-
module PulseMeter
|
2
|
-
module Visualize
|
3
|
-
module DSL
|
4
|
-
class Widget
|
5
|
-
include PulseMeter::Mixins::Utils
|
6
|
-
|
7
|
-
DEFAULT_WIDTH = 10
|
8
|
-
DEFAULT_TIMESPAN = 60 * 60 * 24 # One day
|
9
|
-
DEFAULT_GCHART_OPTIONS = {}
|
10
|
-
|
11
|
-
def initialize(type, title = '')
|
12
|
-
raise BadWidgetType, type if type.to_s.empty?
|
13
|
-
@type = type
|
14
|
-
@title = title.to_s || ''
|
15
|
-
@values_label = ''
|
16
|
-
@width = DEFAULT_WIDTH
|
17
|
-
@sensors = []
|
18
|
-
@show_last_point = false
|
19
|
-
@redraw_interval = nil
|
20
|
-
@timespan = DEFAULT_TIMESPAN
|
21
|
-
@gchart_options = DEFAULT_GCHART_OPTIONS.dup
|
22
|
-
end
|
23
|
-
|
24
|
-
def process_args(args)
|
25
|
-
[:sensor, :title, :width, :values_label, :show_last_point, :redraw_interval, :timespan].each do |arg|
|
26
|
-
if args.has_key?(arg)
|
27
|
-
send(arg, args[arg])
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def redraw_interval(new_redraw_interval)
|
33
|
-
new_redraw_interval = new_redraw_interval.to_i
|
34
|
-
raise BadWidgetRedrawInterval, new_redraw_interval unless new_redraw_interval > 0
|
35
|
-
@redraw_interval = new_redraw_interval
|
36
|
-
end
|
37
|
-
|
38
|
-
def show_last_point(new_show_last_point)
|
39
|
-
@show_last_point = !!new_show_last_point
|
40
|
-
end
|
41
|
-
|
42
|
-
def timespan(new_timespan)
|
43
|
-
new_timespan = new_timespan.to_i
|
44
|
-
raise BadWidgetTimeSpan, new_timespan unless new_timespan > 0
|
45
|
-
@timespan = new_timespan
|
46
|
-
end
|
47
|
-
|
48
|
-
def values_label(new_label)
|
49
|
-
@values_label = new_label.to_s
|
50
|
-
end
|
51
|
-
|
52
|
-
def title(new_title)
|
53
|
-
@title = new_title.to_s || ''
|
54
|
-
end
|
55
|
-
|
56
|
-
def width(new_width)
|
57
|
-
raise BadWidgetWidth, new_width unless new_width.respond_to?(:to_i)
|
58
|
-
w = new_width.to_i
|
59
|
-
raise BadWidgetWidth, new_width unless w > 0 && w <= 10
|
60
|
-
@width = new_width.to_i
|
61
|
-
end
|
62
|
-
|
63
|
-
def gchart_options(options = {})
|
64
|
-
@gchart_options.merge!(options)
|
65
|
-
end
|
66
|
-
|
67
|
-
def sensor(name, sensor_args = nil)
|
68
|
-
s = PulseMeter::Visualize::DSL::Sensor.new(name)
|
69
|
-
s.process_args(sensor_args) if sensor_args
|
70
|
-
@sensors << s
|
71
|
-
end
|
72
|
-
|
73
|
-
def method_missing(name, value)
|
74
|
-
@gchart_options[name] = value
|
75
|
-
end
|
76
|
-
|
77
|
-
def to_widget
|
78
|
-
args = {
|
79
|
-
title: @title,
|
80
|
-
type: @type,
|
81
|
-
values_label: @values_label,
|
82
|
-
width: @width,
|
83
|
-
sensors: @sensors.map(&:to_sensor),
|
84
|
-
redraw_interval: @redraw_interval,
|
85
|
-
show_last_point: @show_last_point,
|
86
|
-
timespan: @timespan,
|
87
|
-
gchart_options: @gchart_options
|
88
|
-
}
|
89
|
-
PulseMeter::Visualize::Widget.new(args)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|