plantwatchdog 0.0.1
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/History.txt +4 -0
- data/License.txt +674 -0
- data/Manifest.txt +42 -0
- data/README.txt +91 -0
- data/Rakefile +22 -0
- data/bin/plantwatchdog +8 -0
- data/bin/upload_measurements +2 -0
- data/config.ru +35 -0
- data/config/app_config.yaml +10 -0
- data/lib/plantwatchdog/aggregation.rb +220 -0
- data/lib/plantwatchdog/aggregation_methods.rb +90 -0
- data/lib/plantwatchdog/data.rb +126 -0
- data/lib/plantwatchdog/db.rb +37 -0
- data/lib/plantwatchdog/gems.rb +5 -0
- data/lib/plantwatchdog/main.rb +76 -0
- data/lib/plantwatchdog/model.rb +442 -0
- data/lib/plantwatchdog/sinatra.rb +206 -0
- data/public/images/arrow-down.gif +0 -0
- data/public/images/arrow-left.gif +0 -0
- data/public/images/arrow-right.gif +0 -0
- data/public/images/arrow-up.gif +0 -0
- data/public/images/spinner.gif +0 -0
- data/public/images/tabs.png +0 -0
- data/public/js/customflot.js +120 -0
- data/public/js/jquery-1.3.2.min.js +19 -0
- data/public/js/jquery.flot.crosshair.js +157 -0
- data/public/js/jquery.flot.js +2119 -0
- data/public/js/jquery.flot.navigate.js +272 -0
- data/public/js/jquery.flot.selection.js +299 -0
- data/public/js/select-chain.js +71 -0
- data/public/js/tools.tabs-1.0.4.js +285 -0
- data/public/tabs.css +87 -0
- data/sample/solar/create_solar.rb +31 -0
- data/sample/solar/measurements/client.sqlite3 +0 -0
- data/sample/solar/static/devices.yml +17 -0
- data/sample/solar/static/metadata.yml +30 -0
- data/sample/solar/static/plants.yml +3 -0
- data/sample/solar/static/users.yml +4 -0
- data/sample/solar/upload_measurements +26 -0
- data/templates/graph.erb +134 -0
- data/templates/index.erb +24 -0
- data/templates/monthly_graph.erb +41 -0
- data/test/test_aggregation.rb +161 -0
- data/test/test_aggregation_methods.rb +50 -0
- data/test/test_base.rb +83 -0
- data/test/test_data.rb +118 -0
- data/test/test_model.rb +142 -0
- data/test/test_sync.rb +71 -0
- data/test/test_web.rb +87 -0
- metadata +167 -0
@@ -0,0 +1,71 @@
|
|
1
|
+
/**
|
2
|
+
* @author Remy Sharp
|
3
|
+
* @date 2008-02-25
|
4
|
+
* @url http://remysharp.com/2007/09/18/auto-populate-multiple-select-boxes/
|
5
|
+
* @license Creative Commons License - ShareAlike http://creativecommons.org/licenses/by-sa/3.0/
|
6
|
+
*/
|
7
|
+
|
8
|
+
|
9
|
+
(function ($) {
|
10
|
+
$.fn.selectChain = function (options, dynamic_data) {
|
11
|
+
var defaults = {
|
12
|
+
key: "id",
|
13
|
+
value: "label"
|
14
|
+
};
|
15
|
+
|
16
|
+
var settings = $.extend({}, defaults, options);
|
17
|
+
|
18
|
+
if (!(settings.target instanceof $)) settings.target = $(settings.target);
|
19
|
+
|
20
|
+
|
21
|
+
return this.each(function () {
|
22
|
+
var $$ = $(this);
|
23
|
+
|
24
|
+
$$.change(function () {
|
25
|
+
var data = null;
|
26
|
+
if (dynamic_data != null) {
|
27
|
+
dynamic_data(settings);
|
28
|
+
}
|
29
|
+
|
30
|
+
settings.target.empty();
|
31
|
+
|
32
|
+
$.ajax({
|
33
|
+
url: settings.url,
|
34
|
+
data: data,
|
35
|
+
type: (settings.type || 'get'),
|
36
|
+
dataType: 'json',
|
37
|
+
success: function (j) {
|
38
|
+
var options = [], i = 0, o = null;
|
39
|
+
|
40
|
+
for (i = 0; i < j.length; i++) {
|
41
|
+
// required to get around IE bug (http://support.microsoft.com/?scid=kb%3Ben-us%3B276228)
|
42
|
+
o = document.createElement("OPTION");
|
43
|
+
o.value = typeof j[i] == 'object' ? j[i][settings.key] : j[i];
|
44
|
+
o.text = typeof j[i] == 'object' ? j[i][settings.value] : j[i];
|
45
|
+
settings.target.get(0).options[i] = o;
|
46
|
+
}
|
47
|
+
|
48
|
+
// hand control back to browser for a moment
|
49
|
+
setTimeout(function () {
|
50
|
+
settings.target
|
51
|
+
.find('option:l')
|
52
|
+
.attr('selected', 'selected')
|
53
|
+
.parent('select')
|
54
|
+
.trigger('change');
|
55
|
+
}, 0);
|
56
|
+
},
|
57
|
+
error: function (xhr, desc, er) {
|
58
|
+
// add whatever debug you want here.
|
59
|
+
alert("an error occurred");
|
60
|
+
}
|
61
|
+
});
|
62
|
+
});
|
63
|
+
});
|
64
|
+
};
|
65
|
+
$.fn.selectedOption = function()
|
66
|
+
{
|
67
|
+
var result = 0;
|
68
|
+
var sels = $(this).each( function() { result = this.options[this.selectedIndex].value});
|
69
|
+
return result;
|
70
|
+
}
|
71
|
+
})(jQuery);
|
@@ -0,0 +1,285 @@
|
|
1
|
+
/**
|
2
|
+
* tools.tabs 1.0.4 - Tabs done right.
|
3
|
+
*
|
4
|
+
* Copyright (c) 2009 Tero Piirainen
|
5
|
+
* http://flowplayer.org/tools/tabs.html
|
6
|
+
*
|
7
|
+
* Dual licensed under MIT and GPL 2+ licenses
|
8
|
+
* http://www.opensource.org/licenses
|
9
|
+
*
|
10
|
+
* Launch : November 2008
|
11
|
+
* Date: ${date}
|
12
|
+
* Revision: ${revision}
|
13
|
+
*/
|
14
|
+
(function($) {
|
15
|
+
|
16
|
+
// static constructs
|
17
|
+
$.tools = $.tools || {};
|
18
|
+
|
19
|
+
$.tools.tabs = {
|
20
|
+
version: '1.0.4',
|
21
|
+
|
22
|
+
conf: {
|
23
|
+
tabs: 'a',
|
24
|
+
current: 'current',
|
25
|
+
onBeforeClick: null,
|
26
|
+
onClick: null,
|
27
|
+
effect: 'default',
|
28
|
+
initialIndex: 0,
|
29
|
+
event: 'click',
|
30
|
+
api:false,
|
31
|
+
rotate: false
|
32
|
+
},
|
33
|
+
|
34
|
+
addEffect: function(name, fn) {
|
35
|
+
effects[name] = fn;
|
36
|
+
}
|
37
|
+
};
|
38
|
+
|
39
|
+
|
40
|
+
var effects = {
|
41
|
+
|
42
|
+
// simple "toggle" effect
|
43
|
+
'default': function(i, done) {
|
44
|
+
this.getPanes().hide().eq(i).show();
|
45
|
+
done.call();
|
46
|
+
},
|
47
|
+
|
48
|
+
/*
|
49
|
+
configuration:
|
50
|
+
- fadeOutSpeed (positive value does "crossfading")
|
51
|
+
- fadeInSpeed
|
52
|
+
*/
|
53
|
+
fade: function(i, done) {
|
54
|
+
var conf = this.getConf(),
|
55
|
+
speed = conf.fadeOutSpeed,
|
56
|
+
panes = this.getPanes();
|
57
|
+
|
58
|
+
if (speed) {
|
59
|
+
panes.fadeOut(speed);
|
60
|
+
} else {
|
61
|
+
panes.hide();
|
62
|
+
}
|
63
|
+
|
64
|
+
panes.eq(i).fadeIn(conf.fadeInSpeed, done);
|
65
|
+
},
|
66
|
+
|
67
|
+
// for basic accordions
|
68
|
+
slide: function(i, done) {
|
69
|
+
this.getPanes().slideUp(200);
|
70
|
+
this.getPanes().eq(i).slideDown(400, done);
|
71
|
+
},
|
72
|
+
|
73
|
+
// simple AJAX effect
|
74
|
+
ajax: function(i, done) {
|
75
|
+
this.getPanes().eq(0).load(this.getTabs().eq(i).attr("href"), done);
|
76
|
+
}
|
77
|
+
|
78
|
+
};
|
79
|
+
|
80
|
+
var w;
|
81
|
+
|
82
|
+
// this is how you add effects
|
83
|
+
$.tools.tabs.addEffect("horizontal", function(i, done) {
|
84
|
+
|
85
|
+
// store original width of a pane into memory
|
86
|
+
if (!w) { w = this.getPanes().eq(0).width(); }
|
87
|
+
|
88
|
+
// set current pane's width to zero
|
89
|
+
this.getCurrentPane().animate({width: 0}, function() { $(this).hide(); });
|
90
|
+
|
91
|
+
// grow opened pane to it's original width
|
92
|
+
this.getPanes().eq(i).animate({width: w}, function() {
|
93
|
+
$(this).show();
|
94
|
+
done.call();
|
95
|
+
});
|
96
|
+
|
97
|
+
});
|
98
|
+
|
99
|
+
|
100
|
+
function Tabs(tabs, panes, conf) {
|
101
|
+
|
102
|
+
var self = this, $self = $(this), current;
|
103
|
+
|
104
|
+
// bind all callbacks from configuration
|
105
|
+
$.each(conf, function(name, fn) {
|
106
|
+
if ($.isFunction(fn)) { $self.bind(name, fn); }
|
107
|
+
});
|
108
|
+
|
109
|
+
|
110
|
+
// public methods
|
111
|
+
$.extend(this, {
|
112
|
+
click: function(i, e) {
|
113
|
+
|
114
|
+
var pane = self.getCurrentPane();
|
115
|
+
var tab = tabs.eq(i);
|
116
|
+
|
117
|
+
if (typeof i == 'string' && i.replace("#", "")) {
|
118
|
+
tab = tabs.filter("[href*=" + i.replace("#", "") + "]");
|
119
|
+
i = Math.max(tabs.index(tab), 0);
|
120
|
+
}
|
121
|
+
|
122
|
+
if (conf.rotate) {
|
123
|
+
var last = tabs.length -1;
|
124
|
+
if (i < 0) { return self.click(last, e); }
|
125
|
+
if (i > last) { return self.click(0, e); }
|
126
|
+
}
|
127
|
+
|
128
|
+
if (!tab.length) {
|
129
|
+
if (current >= 0) { return self; }
|
130
|
+
i = conf.initialIndex;
|
131
|
+
tab = tabs.eq(i);
|
132
|
+
}
|
133
|
+
|
134
|
+
// current tab is being clicked
|
135
|
+
if (i === current) { return self; }
|
136
|
+
|
137
|
+
// possibility to cancel click action
|
138
|
+
e = e || $.Event();
|
139
|
+
e.type = "onBeforeClick";
|
140
|
+
$self.trigger(e, [i]);
|
141
|
+
if (e.isDefaultPrevented()) { return; }
|
142
|
+
|
143
|
+
// call the effect
|
144
|
+
effects[conf.effect].call(self, i, function() {
|
145
|
+
|
146
|
+
// onClick callback
|
147
|
+
e.type = "onClick";
|
148
|
+
$self.trigger(e, [i]);
|
149
|
+
});
|
150
|
+
|
151
|
+
// onStart
|
152
|
+
e.type = "onStart";
|
153
|
+
$self.trigger(e, [i]);
|
154
|
+
if (e.isDefaultPrevented()) { return; }
|
155
|
+
|
156
|
+
// default behaviour
|
157
|
+
current = i;
|
158
|
+
tabs.removeClass(conf.current);
|
159
|
+
tab.addClass(conf.current);
|
160
|
+
|
161
|
+
return self;
|
162
|
+
},
|
163
|
+
|
164
|
+
getConf: function() {
|
165
|
+
return conf;
|
166
|
+
},
|
167
|
+
|
168
|
+
getTabs: function() {
|
169
|
+
return tabs;
|
170
|
+
},
|
171
|
+
|
172
|
+
getPanes: function() {
|
173
|
+
return panes;
|
174
|
+
},
|
175
|
+
|
176
|
+
getCurrentPane: function() {
|
177
|
+
return panes.eq(current);
|
178
|
+
},
|
179
|
+
|
180
|
+
getCurrentTab: function() {
|
181
|
+
return tabs.eq(current);
|
182
|
+
},
|
183
|
+
|
184
|
+
getIndex: function() {
|
185
|
+
return current;
|
186
|
+
},
|
187
|
+
|
188
|
+
next: function() {
|
189
|
+
return self.click(current + 1);
|
190
|
+
},
|
191
|
+
|
192
|
+
prev: function() {
|
193
|
+
return self.click(current - 1);
|
194
|
+
},
|
195
|
+
|
196
|
+
bind: function(name, fn) {
|
197
|
+
$self.bind(name, fn);
|
198
|
+
return self;
|
199
|
+
},
|
200
|
+
|
201
|
+
onBeforeClick: function(fn) {
|
202
|
+
return this.bind("onBeforeClick", fn);
|
203
|
+
},
|
204
|
+
|
205
|
+
onClick: function(fn) {
|
206
|
+
return this.bind("onClick", fn);
|
207
|
+
},
|
208
|
+
|
209
|
+
unbind: function(name) {
|
210
|
+
$self.unbind(name);
|
211
|
+
return self;
|
212
|
+
}
|
213
|
+
|
214
|
+
});
|
215
|
+
|
216
|
+
|
217
|
+
// setup click actions for each tab
|
218
|
+
tabs.each(function(i) {
|
219
|
+
$(this).bind(conf.event, function(e) {
|
220
|
+
self.click(i, e);
|
221
|
+
return false;
|
222
|
+
});
|
223
|
+
});
|
224
|
+
|
225
|
+
// if no pane is visible --> click on the first tab
|
226
|
+
if (location.hash) {
|
227
|
+
self.click(location.hash);
|
228
|
+
} else {
|
229
|
+
if (conf.initialIndex === 0 || conf.initialIndex > 0) {
|
230
|
+
self.click(conf.initialIndex);
|
231
|
+
}
|
232
|
+
}
|
233
|
+
|
234
|
+
// cross tab anchor link
|
235
|
+
panes.find("a[href^=#]").click(function(e) {
|
236
|
+
self.click($(this).attr("href"), e);
|
237
|
+
});
|
238
|
+
}
|
239
|
+
|
240
|
+
|
241
|
+
// jQuery plugin implementation
|
242
|
+
$.fn.tabs = function(query, conf) {
|
243
|
+
|
244
|
+
// return existing instance
|
245
|
+
var el = this.eq(typeof conf == 'number' ? conf : 0).data("tabs");
|
246
|
+
if (el) { return el; }
|
247
|
+
|
248
|
+
if ($.isFunction(conf)) {
|
249
|
+
conf = {onBeforeClick: conf};
|
250
|
+
}
|
251
|
+
|
252
|
+
// setup options
|
253
|
+
var globals = $.extend({}, $.tools.tabs.conf), len = this.length;
|
254
|
+
conf = $.extend(globals, conf);
|
255
|
+
|
256
|
+
|
257
|
+
// install tabs for each items in jQuery
|
258
|
+
this.each(function(i) {
|
259
|
+
var root = $(this);
|
260
|
+
|
261
|
+
// find tabs
|
262
|
+
var els = root.find(conf.tabs);
|
263
|
+
|
264
|
+
if (!els.length) {
|
265
|
+
els = root.children();
|
266
|
+
}
|
267
|
+
|
268
|
+
// find panes
|
269
|
+
var panes = query.jquery ? query : root.children(query);
|
270
|
+
|
271
|
+
if (!panes.length) {
|
272
|
+
panes = len == 1 ? $(query) : root.parent().find(query);
|
273
|
+
}
|
274
|
+
|
275
|
+
el = new Tabs(els, panes, conf);
|
276
|
+
root.data("tabs", el);
|
277
|
+
|
278
|
+
});
|
279
|
+
|
280
|
+
return conf.api ? el: this;
|
281
|
+
};
|
282
|
+
|
283
|
+
}) (jQuery);
|
284
|
+
|
285
|
+
|
data/public/tabs.css
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
/* root element for tabs */
|
2
|
+
ul.tabs {
|
3
|
+
list-style:none;
|
4
|
+
margin:0 !important;
|
5
|
+
padding:0;
|
6
|
+
border-bottom:1px solid #666;
|
7
|
+
height:30px;
|
8
|
+
}
|
9
|
+
|
10
|
+
/* single tab */
|
11
|
+
ul.tabs li {
|
12
|
+
float:left;
|
13
|
+
text-indent:0;
|
14
|
+
padding:0;
|
15
|
+
margin:0 !important;
|
16
|
+
list-style-image:none !important;
|
17
|
+
}
|
18
|
+
|
19
|
+
/* link inside the tab. uses a background image */
|
20
|
+
ul.tabs a {
|
21
|
+
background: url(images/tabs.png) no-repeat -420px 0;
|
22
|
+
font-size:11px;
|
23
|
+
display:block;
|
24
|
+
height: 30px;
|
25
|
+
line-height:30px;
|
26
|
+
width: 134px;
|
27
|
+
text-align:center;
|
28
|
+
text-decoration:none;
|
29
|
+
color:#333;
|
30
|
+
padding:0px;
|
31
|
+
margin:0px;
|
32
|
+
position:relative;
|
33
|
+
top:1px;
|
34
|
+
}
|
35
|
+
|
36
|
+
ul.tabs a:active {
|
37
|
+
outline:none;
|
38
|
+
}
|
39
|
+
|
40
|
+
/* when mouse enters the tab move the background image */
|
41
|
+
ul.tabs a:hover {
|
42
|
+
background-position: -420px -31px;
|
43
|
+
color:#fff;
|
44
|
+
}
|
45
|
+
|
46
|
+
/* active tab uses a class name "current". it's highlight is also done by moving the background image. */
|
47
|
+
ul.tabs a.current, ul.tabs a.current:hover, ul.tabs li.current a {
|
48
|
+
background-position: -420px -62px;
|
49
|
+
cursor:default !important;
|
50
|
+
color:#000 !important;
|
51
|
+
}
|
52
|
+
|
53
|
+
/* Different widths for tabs: use a class name: w1, w2, w3 or w2 */
|
54
|
+
|
55
|
+
|
56
|
+
/* width 1 */
|
57
|
+
ul.tabs a.s { background-position: -553px 0; width:81px; }
|
58
|
+
ul.tabs a.s:hover { background-position: -553px -31px; }
|
59
|
+
ul.tabs a.s.current { background-position: -553px -62px; }
|
60
|
+
|
61
|
+
/* width 2 */
|
62
|
+
ul.tabs a.l { background-position: -248px -0px; width:174px; }
|
63
|
+
ul.tabs a.l:hover { background-position: -248px -31px; }
|
64
|
+
ul.tabs a.l.current { background-position: -248px -62px; }
|
65
|
+
|
66
|
+
|
67
|
+
/* width 3 */
|
68
|
+
ul.tabs a.xl { background-position: 0 -0px; width:248px; }
|
69
|
+
ul.tabs a.xl:hover { background-position: 0 -31px; }
|
70
|
+
ul.tabs a.xl.current { background-position: 0 -62px; }
|
71
|
+
|
72
|
+
|
73
|
+
/* initially all panes are hidden */
|
74
|
+
div.panes div.pane {
|
75
|
+
display:none;
|
76
|
+
}
|
77
|
+
|
78
|
+
|
79
|
+
div.panes > div {
|
80
|
+
padding:15px 10px;
|
81
|
+
border:1px solid #999;
|
82
|
+
border-top:0px;
|
83
|
+
font-size:14px;
|
84
|
+
background-color:#fff;
|
85
|
+
}
|
86
|
+
|
87
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__),"..","lib")
|
2
|
+
require 'rubygems'
|
3
|
+
require 'plantwatchdog/model'
|
4
|
+
require 'active_record'
|
5
|
+
require 'active_record/fixtures'
|
6
|
+
|
7
|
+
module PlantWatchdog
|
8
|
+
class CreateSolar
|
9
|
+
def CreateSolar.create
|
10
|
+
PlantWatchdog::Model::Schema.migrate(:up) unless ActiveRecord::Base.connection.table_exists? :users
|
11
|
+
|
12
|
+
fixture_path = File.join(File.dirname(__FILE__),"static")
|
13
|
+
p "Reading fixtures from #{fixture_path}"
|
14
|
+
|
15
|
+
table_names = Dir["#{fixture_path}/*.yml"].map! { |f| File.basename(f).split('.').first.to_s }
|
16
|
+
p "Found content for tables ", table_names.join(" ")
|
17
|
+
class_names = table_names.inject({ :metadata => "PlantWatchdog::Model::Metadata"}) {
|
18
|
+
|result, table_name|
|
19
|
+
key= table_name.to_sym
|
20
|
+
result[key] = "PlantWatchdog::Model::" + table_name.singularize.capitalize unless result[key]
|
21
|
+
result
|
22
|
+
}
|
23
|
+
|
24
|
+
fs = Fixtures.create_fixtures(fixture_path, table_names, class_names)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
#user = PlantWatchdog::Model::User.find(:all).first
|
30
|
+
#p user.plant.aggrules
|
31
|
+
#p user.plant.devices.first.aggrules
|