automate-em 0.0.1 → 0.0.2

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.
@@ -0,0 +1,439 @@
1
+ /**
2
+ * ACA Control jQuery Interface Library
3
+ * Simplifies communication with an ACA Control server when using jQuery
4
+ *
5
+ * Copyright (c) 2011 Advanced Control and Acoustics.
6
+ *
7
+ * @author Stephen von Takach <steve@advancedcontrol.com.au>
8
+ * @copyright 2011 advancedcontrol.com.au
9
+ *
10
+ * --- Usage ---
11
+ * Connecting to an ACA Control Server:
12
+ var controller = new AutomateEm.EventsDispatcher({
13
+ url: 'http://my.control.server.org:8080/',
14
+ one_time_key: '/key/url'
15
+ });
16
+
17
+ * Binding to an event:
18
+ controller.bind('some_event', function(data){
19
+ alert(data.name + ' says: ' + data.message)
20
+ });
21
+
22
+ * Sending a command to the server:
23
+ controller.send('Module.function', "item1", ['item', 2], {item:3}, 4, 5.0, ...);
24
+
25
+ *
26
+ **/
27
+
28
+
29
+ var AutomateEm = {
30
+ Off: false,
31
+ On: true,
32
+ Controllers: [], // So we can inspect all instances
33
+ EventsDispatcher: function (options) {
34
+ //
35
+ // options contains: url, system calls, system
36
+ //
37
+ var config = {
38
+ servers: null, // List of ACA Control Servers in priority order
39
+ system: null, // The system we are connecting to and authentication details for re-connects
40
+ auto_auth: null, // Are we using the manifest authentication system
41
+ idle_update: null, // Do we want to auto-update the interface when it is idle
42
+ forced_update: null,// Update the interface as soon as possible
43
+ },
44
+ state ={
45
+ connection: null, // Base web socket
46
+ connected: true, // Are we currently connected (initialised to true so that any initial failure is triggered)
47
+ ready: false, // Is the server ready for bindings and commands
48
+ polling: false, // Are we polling to remain connected when there is little activity
49
+ resume: false, // The reference to the resume timer
50
+ updater: false, // The reference to the update timer
51
+ offline: false // The system has indicated that it is offline
52
+ },
53
+ bindings = {},
54
+ system_calls = {
55
+ open: true, // Connection to the server has been made
56
+ close: true, // Connection to the server was closed or lost
57
+ ls: true, // List of control systems available to the current user (paginated)
58
+ ready: true, // Remote System is ready for bindings
59
+ authenticate: true, // Authentication required
60
+ system: true, // Please select a system
61
+ pong: true, // System is idle
62
+ offline: true // System is offline (slow down re-connect periods)
63
+ },
64
+ $this = this;
65
+ $.extend(config, options);
66
+ this.config = config; // Allow external access
67
+
68
+
69
+ //
70
+ // Sends a command to the server in the appropriate format
71
+ //
72
+ var send = function (command_name) {
73
+ var payload = JSON.stringify({ command: command_name, data: Array.prototype.slice.call( arguments, 1 ) });
74
+ // Array.prototype.slice.call( arguments, 1 ) gets all the arguments - the first in an array
75
+
76
+ if (state.connection != null && state.connection.readyState == state.connection.OPEN) {
77
+ state.connection.send(payload); // <= send JSON data to socket server
78
+ set_poll(); // Reset polling
79
+ }
80
+
81
+ return $this; // chainable
82
+ };
83
+ this.send = send;
84
+
85
+ //
86
+ // Requests to recieve notifications of a value change from the server
87
+ // Triggers the functions passed in when the server informs us of an update
88
+ //
89
+ this.bind = function (events, func) {
90
+ if(!!func) {
91
+ var temp = {};
92
+ temp[events] = func;
93
+ events = temp;
94
+ }
95
+
96
+ jQuery.each( events, function(event_name, callback){
97
+ bindings[event_name] = bindings[event_name] || [];
98
+ bindings[event_name].push(callback);
99
+
100
+ if (!system_calls[event_name] && state.connection != null && state.connection.readyState == state.connection.OPEN){
101
+ var args = event_name.split('.');
102
+ args.splice(0,0, 'register');
103
+ send.apply( this, args );
104
+ }
105
+ });
106
+
107
+ return this; // chainable
108
+ };
109
+
110
+ this.is_connected = function(){
111
+ return state.connected;
112
+ };
113
+
114
+ //
115
+ // Removes all the callbacks for the event and lets the server know that we
116
+ // don't want to revieve it anymore.
117
+ //
118
+ this.unbind = function (event_name) {
119
+ delete bindings[event_name];
120
+
121
+ if (state.connection != null && state.connection.readyState == state.connection.OPEN){
122
+ var args = event_name.split('.');
123
+ args.splice(0,0, 'unregister');
124
+ send.apply( this, args );
125
+ }
126
+
127
+ return this; // chainable
128
+ };
129
+
130
+ //
131
+ // The event trigger, calls the registered handlers in order
132
+ //
133
+ var trigger = function (event_name, message) {
134
+ var chain = bindings[event_name];
135
+ if (chain === undefined) return; // no bindings for this event
136
+
137
+ var i, result;
138
+ for (i = 0; i < chain.length; i = i + 1) {
139
+ try {
140
+ result = chain[i](message);
141
+ if(result === false) // Return false to prevent later bindings
142
+ break;
143
+ } catch (err) { } // Catch any user code errors
144
+ }
145
+
146
+ return $this; // chainable
147
+ };
148
+ this.trigger = trigger;
149
+
150
+
151
+ //
152
+ // Polling functions
153
+ // Only called if no other traffic is being transmitted.
154
+ //
155
+ function set_poll(){
156
+ if(!!state.polling) {
157
+ clearInterval(state.polling);
158
+ }
159
+ state.polling = setInterval(do_poll, 60000); // Maintain the connection by pinging every 1min
160
+ }
161
+
162
+ function do_poll(){
163
+ if((!!config.idle_update) && updateReady) // Update on idle
164
+ window.location.reload();
165
+ else
166
+ send('ping');
167
+ }
168
+
169
+
170
+ //
171
+ // Sets up a new connection to the remote server
172
+ //
173
+ function setup_connection() {
174
+ jQuery.ajax('/tokens/servers', {
175
+ type: 'GET',
176
+ dataType: 'json',
177
+ success: function(data, textStatus, jqXHR){
178
+ config.servers = data;
179
+
180
+ if(!!window.WebSocket)
181
+ state.connection = new window.WebSocket("ws://" + config.servers[0].hostname + ":81/");
182
+ else if(!!window.MozWebSocket)
183
+ state.connection = new window.MozWebSocket("ws://" + config.servers[0].hostname + ":81/");
184
+ else
185
+ return;
186
+
187
+ // dispatch to the right handlers
188
+ state.connection.onmessage = function (evt) {
189
+ var json = JSON.parse(evt.data);
190
+
191
+ if (json.event == 'ready') {
192
+ //
193
+ // Re-register status events then call ready
194
+ //
195
+ for (event_name in bindings) {
196
+ try {
197
+ if(!system_calls[event_name]) {
198
+ var args = event_name.split('.');
199
+ args.splice(0,0, 'register');
200
+ send.apply( this, args );
201
+ }
202
+ } catch (err) { }
203
+ }
204
+
205
+ set_poll(); // Set the polling to occur
206
+ }
207
+
208
+ //
209
+ // Trigger callbacks
210
+ //
211
+ trigger(json.event, json.data);
212
+ };
213
+
214
+ state.connection.onclose = function () {
215
+ if (state.connected) {
216
+ state.connected = false;
217
+ if(!!state.polling) {
218
+ clearInterval(state.polling);
219
+ state.polling = false;
220
+ }
221
+ trigger('close');
222
+ }
223
+ }
224
+ state.connection.onopen = function () {
225
+ state.connected = true; // prevent multiple disconnect triggers
226
+ trigger('open');
227
+ }
228
+ },
229
+ error: function(jqXHR, textStatus, errorThrown){
230
+ //
231
+ // Damn...
232
+ //
233
+ ;
234
+ }
235
+ });
236
+ }
237
+ setup_connection();
238
+
239
+
240
+ //
241
+ // Ensure the connection is resumed if disconnected
242
+ // We do this in this way for mobile devices when resumed from sleep to ensure they reconnect
243
+ //
244
+ function checkResume() {
245
+ if (state.connection == null || state.connection.readyState == state.connection.CLOSED) {
246
+ setup_connection();
247
+ }
248
+ }
249
+ state.resume = window.setInterval(checkResume, 1000);
250
+ AutomateEm.Controllers.push(this);
251
+
252
+
253
+ //
254
+ // Bind events for automation (first attempt, else fall back to interface for both auth and system)
255
+ //
256
+ var authCount = 0,
257
+ sysCallCount = 0,
258
+ oneKey;
259
+
260
+ this.bind('open', function(){
261
+
262
+ authCount = 0;
263
+ sysCallCount = 0;
264
+ oneKey = null;
265
+
266
+ });
267
+
268
+ function getCookie(c_name)
269
+ {
270
+ var i,x,y,ARRcookies=document.cookie.split(";");
271
+ for (i=0;i<ARRcookies.length;i++)
272
+ {
273
+ x=ARRcookies[i].substr(0,ARRcookies[i].indexOf("="));
274
+ y=ARRcookies[i].substr(ARRcookies[i].indexOf("=")+1);
275
+ x=x.replace(/^\s+|\s+$/g,"");
276
+ if (x==c_name)
277
+ {
278
+ return unescape(y);
279
+ }
280
+ }
281
+ return null;
282
+ }
283
+
284
+ this.bind('authenticate', function(){
285
+ authCount += 1;
286
+
287
+ if(authCount == 1 && (!!config.auto_auth) && (!!config.system)) {
288
+ oneKey = getCookie('next_key');
289
+ if(!!oneKey){
290
+ send("authenticate", oneKey);
291
+ return false;
292
+ }
293
+ }
294
+
295
+ authCount += 1;
296
+ });
297
+
298
+ this.bind('system', function(){
299
+ sysCallCount += 1;
300
+
301
+ if(sysCallCount == 1 && config.system !== false) { // 0 == false
302
+ send("system", config.system);
303
+
304
+ return false;
305
+ } // Auto login failure here will result in a disconnect
306
+ });
307
+
308
+ this.bind('offline', function(){
309
+ state.offline = true;
310
+ clearInterval(state.resume);
311
+ state.resume = window.setInterval(checkResume, 15000);
312
+ });
313
+
314
+ this.bind('ready', function(){
315
+ if(state.offline) {
316
+ state.offline = false;
317
+ clearInterval(state.resume);
318
+ state.resume = window.setInterval(checkResume, 1000);
319
+ }
320
+
321
+ if((!!config.auto_auth) && (!!config.system) && sysCallCount == 1 && authCount == 1) {
322
+ //
323
+ // Authenticate with the server
324
+ // Then we can safely reload the cache
325
+ // on cache success we accept the new key
326
+ //
327
+ jQuery.ajax('/tokens/new', {
328
+ type: 'GET',
329
+ dataType: 'text',
330
+ success: function(data, textStatus, jqXHR){
331
+ //
332
+ // Set the csrf token
333
+ // Get the new one-time-key
334
+ //
335
+ $('meta[name="csrf-token"]').attr('content', data);
336
+
337
+ jQuery.ajax('/tokens/authenticate', {
338
+ type: 'POST',
339
+ data: {
340
+ key: oneKey,
341
+ system: config.system
342
+ },
343
+ dataType: 'text',
344
+ success: function(data, textStatus, jqXHR){
345
+ // Accept the new key on success
346
+ jQuery.ajax('/tokens/accept', {
347
+ type: 'POST',
348
+ data: {
349
+ key: oneKey,
350
+ system: config.system
351
+ },
352
+ dataType: 'text',
353
+ success: function(data, textStatus, jqXHR){
354
+ //
355
+ // This can safely be ignored. Here for debugging
356
+ //
357
+ var yay = "success";
358
+ },
359
+ error: function(){
360
+ //
361
+ // This can safely be ignored. Here for debugging
362
+ //
363
+ var damn = "fail";
364
+ }
365
+ });
366
+ },
367
+ error: function(){
368
+ //
369
+ // This can safely be ignored. Here for debugging
370
+ //
371
+ var damn = "fail";
372
+ }
373
+ });
374
+ },
375
+ error: function(){
376
+ //
377
+ // This can safely be ignored. Here for debugging
378
+ //
379
+ var damn = "fail";
380
+ }
381
+ });
382
+ }
383
+
384
+ authCount = 0;
385
+ sysCallCount = 0;
386
+ });
387
+
388
+ //
389
+ // End Auto_auth ---------------
390
+ //
391
+
392
+
393
+ //
394
+ // Auto update functions
395
+ //
396
+ var appCache = window.applicationCache,
397
+ updateReady = false;
398
+
399
+ function bindCache(){
400
+
401
+ $(appCache).bind('updateready', function(){
402
+ appCache.swapCache(); // Swap cache has called key
403
+
404
+ if(!!config.forced_update)
405
+ window.location.reload();
406
+ else {
407
+ appCache = window.applicationCache;
408
+ bindCache();
409
+ updateReady = true;
410
+ }
411
+ });
412
+
413
+ }
414
+
415
+ if((!!config.idle_update) || (!!config.forced_update)) {
416
+ bindCache();
417
+
418
+ state.updater = setInterval('window.applicationCache.update();', 600000);
419
+ }
420
+
421
+ //
422
+ // Disconnects and removes the self reference to the object
423
+ // Once all external references are removed it will be garbage collected
424
+ //
425
+ this.destroy = function(){
426
+ clearInterval(state.resume);
427
+ clearInterval(state.updater);
428
+ if(state.connection != null)
429
+ state.connection.close();
430
+ var i;
431
+ for(i = 0; i < AutomateEm.Controllers.length; i = i + 1) {
432
+ if(AutomateEm.Controllers[i] == this) {
433
+ AutomateEm.Controllers.splice(i, 1);
434
+ break;
435
+ }
436
+ }
437
+ };
438
+ }
439
+ };
@@ -0,0 +1,99 @@
1
+ require 'uri'
2
+
3
+ class TokensController < ActionController::Base
4
+
5
+ protect_from_forgery
6
+ before_filter :auth_user, :only => [:accept]
7
+ layout nil
8
+
9
+
10
+ def authenticate # Allowed through by application controller
11
+ #
12
+ # Auth(gen)
13
+ # check the system matches (set user and system in session)
14
+ # respond with success
15
+ #
16
+ dev = TrustedDevice.try_to_login(params[:key], true) # true means gen the next key
17
+ if params[:system].present? && dev.present? && params[:system].to_i == dev.control_system_id
18
+ session[:token] = dev.user_id
19
+ session[:system] = dev.control_system_id
20
+ session[:key] = params[:key]
21
+ cookies.permanent[:next_key] = {:value => dev.next_key, :path => URI.parse(request.referer).path}
22
+
23
+ render :nothing => true # success!
24
+ else
25
+ render :nothing => true, :status => :forbidden # 403
26
+ end
27
+ end
28
+
29
+
30
+ def accept
31
+ dev = TrustedDevice.where('user_id = ? AND control_system_id = ? AND one_time_key = ? AND (expires IS NULL OR expires > ?)',
32
+ session[:token], session[:system], session[:key], Time.now).first
33
+
34
+ if dev.present?
35
+ dev.accept_key
36
+ render :nothing => true # success!
37
+ else
38
+ render :nothing => true, :status => :forbidden # 403
39
+ end
40
+ end
41
+
42
+
43
+ #
44
+ # Build a new session for the interface if the existing one has expired
45
+ # This maintains the csrf security
46
+ # We don't want to reset the session if a valid user is already authenticated either
47
+ #
48
+ def new
49
+ reset_session unless session[:user].present?
50
+
51
+ render :text => form_authenticity_token
52
+ end
53
+
54
+
55
+ def create
56
+ #
57
+ # Application controller ensures we are logged in as real user
58
+ # Ensure the user can access the control system requested (the control system does this too)
59
+ # Generate key, populate the session
60
+ #
61
+ user = session[:user].present? ? User.find(session[:user]) : nil # We have to be authed to get here
62
+ sys = user.control_systems.where('control_systems.id = ?', params[:system]).first unless user.nil?
63
+ if user.present? && sys.present?
64
+
65
+ dev = TrustedDevice.new
66
+ dev.reason = params[:trusted_device][:reason]
67
+ dev.user = user
68
+ dev.control_system = sys
69
+ dev.save
70
+
71
+ if !dev.new_record?
72
+ cookies.permanent[:next_key] = {:value => dev.one_time_key, :path => URI.parse(request.referer).path}
73
+ render :json => {} # success!
74
+ else
75
+ render :json => dev.errors.messages, :status => :not_acceptable # 406
76
+ end
77
+ else
78
+ if user.present?
79
+ render :json => {:control => 'could not find the system selected'}, :status => :forbidden # 403
80
+ else
81
+ render :json => {:you => 'are not authorised'}, :status => :forbidden # 403
82
+ end
83
+ end
84
+ end
85
+
86
+
87
+ def servers
88
+ render :json => Server.all
89
+ end
90
+
91
+
92
+ protected
93
+
94
+
95
+ def auth_user
96
+ redirect_to root_path unless session[:user].present? || session[:token].present?
97
+ end
98
+
99
+ end
@@ -19,7 +19,7 @@ class Setting < ActiveRecord::Base
19
19
  if self[:text_value].blank?
20
20
  self[:text_value] = ""
21
21
  else
22
- self[:text_value] = Encryptor.decrypt(Base64.decode64(self[:text_value]), {:key => CONTROL_CONFIG[:encrypt_key], :algorithm => 'aes-256-cbc'})
22
+ self[:text_value] = Encryptor.decrypt(Base64.decode64(self[:text_value]), {:key => Rails.application.config.automate.encrypt_key, :algorithm => 'aes-256-cbc'})
23
23
  end
24
24
  end
25
25
 
@@ -29,7 +29,7 @@ class Setting < ActiveRecord::Base
29
29
  if self[:text_value].blank?
30
30
  self[:text_value] = ""
31
31
  else
32
- self[:text_value] = Base64.encode64(Encryptor.encrypt(self[:text_value], {:key => CONTROL_CONFIG[:encrypt_key], :algorithm => 'aes-256-cbc'}))
32
+ self[:text_value] = Base64.encode64(Encryptor.encrypt(self[:text_value], {:key => Rails.application.config.automate.encrypt_key, :algorithm => 'aes-256-cbc'}))
33
33
  end
34
34
  end
35
35
 
data/config/routes.rb ADDED
@@ -0,0 +1,12 @@
1
+ Rails.application.routes.draw do
2
+
3
+ #
4
+ # TODO:: Namespace this
5
+ #
6
+ resources :tokens do
7
+ post :authenticate, :on => :collection
8
+ post :accept, :on => :collection
9
+ get :servers, :on => :collection
10
+ end
11
+
12
+ end
@@ -14,20 +14,21 @@ module AutomateEm
14
14
  @@load_lock.mon_synchronize {
15
15
  begin
16
16
  found = false
17
+ file = "#{dep.classname.underscore}.rb"
17
18
 
18
19
  Rails.configuration.automate.module_paths.each do |path|
19
- if File.exists?("#{path}/#{dep.filename}")
20
- load "#{path}/#{dep.filename}"
20
+ if File.exists?("#{path}/#{file}")
21
+ load "#{path}/#{file}"
21
22
  found = true
22
23
  break
23
24
  end
24
25
  end
25
26
 
26
27
  if not found
27
- raise "File not found!"
28
+ raise "File not found! (#{file})"
28
29
  end
29
30
 
30
- @@modules[dep.id] = dep.classname.classify.constantize
31
+ @@modules[dep.id] = dep.classname.constantize
31
32
  rescue => e
32
33
  AutomateEm.print_error(System.logger, e, {
33
34
  :message => "device module #{dep.actual_name} error whilst loading",
@@ -116,12 +117,15 @@ module AutomateEm
116
117
  #
117
118
  # Instance of a user module
118
119
  #
119
- @instance = Modules[controllerDevice.dependency_id].new(controllerDevice.tls, controllerDevice.makebreak)
120
- @instance.join_system(@system)
121
- @@instances[@device] = @instance
122
- @@devices[baselookup] = @instance
123
- @@lookup[@instance] = [@device]
124
- @@lookup_lock.unlock #UNLOCK!! so we can lookup settings in on_load
120
+ begin
121
+ @instance = Modules[controllerDevice.dependency_id].new(controllerDevice.tls, controllerDevice.makebreak)
122
+ @instance.join_system(@system)
123
+ @@instances[@device] = @instance
124
+ @@devices[baselookup] = @instance
125
+ @@lookup[@instance] = [@device]
126
+ ensure
127
+ @@lookup_lock.unlock #UNLOCK!! so we can lookup settings in on_load
128
+ end
125
129
 
126
130
  devBase = nil
127
131
 
@@ -179,13 +183,16 @@ module AutomateEm
179
183
  #
180
184
  # add parent may lock at this point!
181
185
  #
182
- @instance = @@devices[baselookup]
183
- @@lookup[@instance] << @device
184
- @@instances[@device] = @instance
185
- EM.defer do
186
- @instance.join_system(@system)
186
+ begin
187
+ @instance = @@devices[baselookup]
188
+ @@lookup[@instance] << @device
189
+ @@instances[@device] = @instance
190
+ EM.defer do
191
+ @instance.join_system(@system)
192
+ end
193
+ ensure
194
+ @@lookup_lock.unlock #UNLOCK!!
187
195
  end
188
- @@lookup_lock.unlock #UNLOCK!!
189
196
  end
190
197
  end
191
198
  end
@@ -190,7 +190,7 @@ module AutomateEm
190
190
  if !@controller.active || force
191
191
  if @logger.nil?
192
192
  if Rails.env.production?
193
- @logger = Logger.new("#{ROOT_DIR}/interface/log/system_#{@controller.id}.log", 10, 4194304)
193
+ @logger = Logger.new(Rails.root.join("log/system_#{@controller.id}.log").to_s, 10, 4194304)
194
194
  else
195
195
  @logger = Logger.new(STDOUT)
196
196
  end
@@ -14,6 +14,7 @@ module AutomateEm
14
14
  app.config.automate = ActiveSupport::OrderedOptions.new
15
15
  app.config.automate.module_paths = []
16
16
  app.config.automate.log_level = Logger::INFO
17
+ app.config.automate.encrypt_key = "Lri2B0yvEVag+raqX9uqMFu9LmGoGwbaO8fzNidf"
17
18
  end
18
19
 
19
20
  #
@@ -1,3 +1,3 @@
1
1
  module AutomateEm
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -12,12 +12,12 @@ class ModuleGenerator < Rails::Generators::NamedBase
12
12
  scope = []
13
13
  text = ""
14
14
  param.map! {|item|
15
- item = item.classify
15
+ item = item.camelcase
16
16
  scope << item
17
17
  text += "module #{scope.join('::')}; end\n"
18
18
  item
19
19
  }
20
- param << name.classify
20
+ param << name.camelcase
21
21
  scope = param.join('::')
22
22
 
23
23
 
@@ -27,7 +27,7 @@ class ModuleGenerator < Rails::Generators::NamedBase
27
27
  text += <<-FILE
28
28
 
29
29
 
30
- class #{scope} < AutomateEm::#{type.downcase.classify}
30
+ class #{scope} < AutomateEm::#{type.downcase.camelcase}
31
31
  def on_load
32
32
  end
33
33
 
@@ -0,0 +1,7 @@
1
+ require 'test_helper'
2
+
3
+ class TokensControllerTest < ActionController::TestCase
4
+ # test "the truth" do
5
+ # assert true
6
+ # end
7
+ end
@@ -0,0 +1,4 @@
1
+ require 'test_helper'
2
+
3
+ class TokensHelperTest < ActionView::TestCase
4
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: automate-em
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-10 00:00:00.000000000 Z
12
+ date: 2012-05-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -194,6 +194,8 @@ executables: []
194
194
  extensions: []
195
195
  extra_rdoc_files: []
196
196
  files:
197
+ - app/assets/javascripts/jquery.automate.js
198
+ - app/controllers/tokens_controller.rb
197
199
  - app/models/controller_device.rb
198
200
  - app/models/controller_http_service.rb
199
201
  - app/models/controller_logic.rb
@@ -205,6 +207,7 @@ files:
205
207
  - app/models/trusted_device.rb
206
208
  - app/models/user_zone.rb
207
209
  - app/models/zone.rb
210
+ - config/routes.rb
208
211
  - db/migrate/20111001022500_init.rb
209
212
  - db/migrate/20111017213801_one_time_key.rb
210
213
  - db/migrate/20111021071632_encrypt_setting.rb
@@ -268,8 +271,10 @@ files:
268
271
  - test/dummy/Rakefile
269
272
  - test/dummy/README.rdoc
270
273
  - test/dummy/script/rails
274
+ - test/functional/tokens_controller_test.rb
271
275
  - test/integration/navigation_test.rb
272
276
  - test/test_helper.rb
277
+ - test/unit/helpers/tokens_helper_test.rb
273
278
  homepage: http://advancedcontrol.com.au/
274
279
  licenses: []
275
280
  post_install_message:
@@ -324,5 +329,7 @@ test_files:
324
329
  - test/dummy/Rakefile
325
330
  - test/dummy/README.rdoc
326
331
  - test/dummy/script/rails
332
+ - test/functional/tokens_controller_test.rb
327
333
  - test/integration/navigation_test.rb
328
334
  - test/test_helper.rb
335
+ - test/unit/helpers/tokens_helper_test.rb