automate-em 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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