pulse-meter 0.2.8 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
-
|