concerto_hardware 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +14 -0
  3. data/README.rdoc +7 -0
  4. data/Rakefile +40 -0
  5. data/app/assets/images/concerto_hardware/helpicon.png +0 -0
  6. data/app/assets/javascripts/concerto_hardware/application.js +15 -0
  7. data/app/assets/javascripts/concerto_hardware/players.js +2 -0
  8. data/app/assets/stylesheets/concerto_hardware/application.css +13 -0
  9. data/app/assets/stylesheets/concerto_hardware/players.css.scss +3 -0
  10. data/app/assets/stylesheets/scaffolds.css.scss +56 -0
  11. data/app/controllers/concerto_hardware/application_controller.rb +16 -0
  12. data/app/controllers/concerto_hardware/players_controller.rb +116 -0
  13. data/app/helpers/players_helper.rb +2 -0
  14. data/app/models/concerto_hardware/ability.rb +42 -0
  15. data/app/models/concerto_hardware/player.rb +169 -0
  16. data/app/views/concerto_hardware/players/_form.html.erb +77 -0
  17. data/app/views/concerto_hardware/players/_show_body.html.erb +45 -0
  18. data/app/views/concerto_hardware/players/_show_header.html.erb +17 -0
  19. data/app/views/concerto_hardware/players/edit.html.erb +9 -0
  20. data/app/views/concerto_hardware/players/index.html.erb +49 -0
  21. data/app/views/concerto_hardware/players/new.html.erb +8 -0
  22. data/app/views/concerto_hardware/players/show.html.erb +8 -0
  23. data/app/views/concerto_hardware/screens/_screen_link.html.erb +49 -0
  24. data/config/locales/en.yml +29 -0
  25. data/config/routes.rb +21 -0
  26. data/db/migrate/20121220000000_create_concerto_hardware_players.rb +12 -0
  27. data/db/migrate/20131127201048_add_updates_to_concerto_hardware_players.rb +6 -0
  28. data/lib/concerto-hardware.rb +6 -0
  29. data/lib/concerto_hardware/engine.rb +68 -0
  30. data/lib/concerto_hardware/version.rb +3 -0
  31. data/test/fixtures/players.yml +13 -0
  32. data/test/functional/players_controller_test.rb +49 -0
  33. data/test/unit/helpers/players_helper_test.rb +4 -0
  34. data/test/unit/player_test.rb +7 -0
  35. metadata +109 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2a8507b255441f28f56a5053cb63e0880e17d40f
4
+ data.tar.gz: 1bdd8dff01bfb754b32b7cc9e2538f573bebc030
5
+ SHA512:
6
+ metadata.gz: a67113f0fd71723c6f450a4d651de1682e35465c1bd3113f23ea12e6d45e8588a4d092f7807398ef480c81b5e9a4efbd09491fa666d20ed2acdab78398ab7f16
7
+ data.tar.gz: e5490b2f6b25587619c9760bb8ea3194a0eca5bb49de639a2b7949c74c342d4c16495a8100bdaef7fb167e0b441ee4260b299496062d2440a3218111e000f4b0
data/LICENSE ADDED
@@ -0,0 +1,14 @@
1
+ Copyright 2011 Concerto Authors
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
14
+
data/README.rdoc ADDED
@@ -0,0 +1,7 @@
1
+ A Rails Engine for managing Bandshell-powered Concerto hardware
2
+
3
+ To use this engine, add the following to the Concerto Gemfile:
4
+ gem 'concerto-hardware', :path => '/path/to/concerto-hardware'
5
+
6
+ To create the proper migrations, use:
7
+ rails generate concerto-hardware
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'ConcertoHardware'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+
27
+
28
+ Bundler::GemHelper.install_tasks
29
+
30
+ require 'rake/testtask'
31
+
32
+ Rake::TestTask.new(:test) do |t|
33
+ t.libs << 'lib'
34
+ t.libs << 'test'
35
+ t.pattern = 'test/**/*_test.rb'
36
+ t.verbose = false
37
+ end
38
+
39
+
40
+ task :default => :test
@@ -0,0 +1,15 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // the compiled file.
9
+ //
10
+ // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
11
+ // GO AFTER THE REQUIRES BELOW.
12
+ //
13
+ //= require jquery
14
+ //= require jquery_ujs
15
+ //= require_tree .
@@ -0,0 +1,2 @@
1
+ // Place all the behaviors and hooks related to the matching controller here.
2
+ // All this logic will automatically be available in application.js.
@@ -0,0 +1,13 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
9
+ * compiled file, but it's generally better to create a new file per style scope.
10
+ *
11
+ *= require_self
12
+ *= require_tree .
13
+ */
@@ -0,0 +1,3 @@
1
+ // Place all the styles related to the Players controller here.
2
+ // They will automatically be included in application.css.
3
+ // You can use Sass (SCSS) here: http://sass-lang.com/
@@ -0,0 +1,56 @@
1
+ body {
2
+ background-color: #fff;
3
+ color: #333;
4
+ font-family: verdana, arial, helvetica, sans-serif;
5
+ font-size: 13px;
6
+ line-height: 18px; }
7
+
8
+ p, ol, ul, td {
9
+ font-family: verdana, arial, helvetica, sans-serif;
10
+ font-size: 13px;
11
+ line-height: 18px; }
12
+
13
+ pre {
14
+ background-color: #eee;
15
+ padding: 10px;
16
+ font-size: 11px; }
17
+
18
+ a {
19
+ color: #000;
20
+ &:visited {
21
+ color: #666; }
22
+ &:hover {
23
+ color: #fff;
24
+ background-color: #000; } }
25
+
26
+ div {
27
+ &.field, &.actions {
28
+ margin-bottom: 10px; } }
29
+
30
+ #notice {
31
+ color: green; }
32
+
33
+ .field_with_errors {
34
+ padding: 2px;
35
+ background-color: red;
36
+ display: table; }
37
+
38
+ #error_explanation {
39
+ width: 450px;
40
+ border: 2px solid red;
41
+ padding: 7px;
42
+ padding-bottom: 0;
43
+ margin-bottom: 20px;
44
+ background-color: #f0f0f0;
45
+ h2 {
46
+ text-align: left;
47
+ font-weight: bold;
48
+ padding: 5px 5px 5px 15px;
49
+ font-size: 12px;
50
+ margin: -7px;
51
+ margin-bottom: 0px;
52
+ background-color: #c00;
53
+ color: #fff; }
54
+ ul li {
55
+ font-size: 12px;
56
+ list-style: square; } }
@@ -0,0 +1,16 @@
1
+ module ConcertoHardware
2
+ # Congratulations! You've found this engine's secret sauce.
3
+ # In a regular isolated engine, the engine's ApplicationController
4
+ # inherits from ActionController::Base. We're using the the main app's
5
+ # ApplicationController, making the isolation a little less strict.
6
+ # For example, we get the layout from the main Concerto app.
7
+ # Note that links back to the main application will need to directly
8
+ # reference the main_app router.
9
+ class ApplicationController < ::ApplicationController
10
+ def current_ability
11
+ # Use the Ability class defined in this engine's namespace.
12
+ # It is designed to also include the rules from the main app.
13
+ @current_ability ||= Ability.new(current_accessor)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,116 @@
1
+ require_dependency "concerto_hardware/application_controller"
2
+
3
+ module ConcertoHardware
4
+ class PlayersController < ApplicationController
5
+ unloadable #marks this class for reloading in between requests
6
+ #include routes.named_routes.helpers
7
+ before_filter :screen_api
8
+
9
+ # GET /players
10
+ # GET /players.json
11
+ def index
12
+ @players = Player.all
13
+ auth!
14
+
15
+ respond_to do |format|
16
+ format.html # index.html.erb
17
+ format.json { render :json => @players }
18
+ end
19
+ end
20
+
21
+ # GET /players/1
22
+ # GET /players/1.json
23
+ # GET /players/by_screen/2(.json)
24
+ # GET /players/current(.json)
25
+ def show
26
+ if params.has_key? :screen_id
27
+ screen_id = params[:screen_id]
28
+ @player = ConcertoHardware::Player.find_by_screen_id!(screen_id)
29
+ elsif params.has_key? :id
30
+ @player = Player.find(params[:id])
31
+ else # Return data about the logged-in screen
32
+ if current_screen.nil?
33
+ raise ActiveRecord::RecordNotFound, "Couldn't find an authenticated screen."
34
+ else
35
+ @player = Player.find_by_screen_id!(current_screen.id)
36
+ end
37
+ end
38
+ auth!
39
+
40
+ respond_to do |format|
41
+ format.html # show.html.erb
42
+ format.json { render :json => @player }
43
+ end
44
+ end
45
+
46
+ # GET /players/new
47
+ # GET /players/new.json
48
+ def new
49
+ @player = Player.new
50
+ if !params[:screen_id].nil?
51
+ # TODO: Error handling
52
+ @player.screen = Screen.find(params[:screen_id])
53
+ end
54
+ auth!
55
+
56
+ respond_to do |format|
57
+ format.html # new.html.erb
58
+ format.json { render :json => @player }
59
+ end
60
+ end
61
+
62
+ # GET /players/1/edit
63
+ def edit
64
+ @player = Player.find(params[:id])
65
+ auth!
66
+ end
67
+
68
+ # POST /players
69
+ # POST /players.json
70
+ def create
71
+ @player = Player.new(params[:player])
72
+ auth!
73
+
74
+ respond_to do |format|
75
+ if @player.save
76
+ format.html { redirect_to [hardware, @player], :notice => 'Player was successfully created.' }
77
+ format.json { render :json => @player, :status => :created, :location => @player }
78
+ else
79
+ format.html { render :action => "new" }
80
+ format.json { render :json => @player.errors, :status => :unprocessable_entity }
81
+ end
82
+ end
83
+ end
84
+
85
+ # PUT /players/1
86
+ # PUT /players/1.json
87
+ def update
88
+ @player = Player.find(params[:id])
89
+ auth!
90
+
91
+ respond_to do |format|
92
+ if @player.update_attributes(params[:player])
93
+ format.html { redirect_to [hardware, @player], :notice => 'Player was successfully updated.' }
94
+ format.json { head :no_content }
95
+ else
96
+ format.html { render :action => "edit" }
97
+ format.json { render :json => @player.errors, :status => :unprocessable_entity }
98
+ end
99
+ end
100
+ end
101
+
102
+ # DELETE /players/1
103
+ # DELETE /players/1.json
104
+ def destroy
105
+ @player = Player.find(params[:id])
106
+ auth!
107
+ @player.destroy
108
+
109
+ respond_to do |format|
110
+ format.html { redirect_to hardware.players_url }
111
+ format.json { head :no_content }
112
+ end
113
+ end
114
+ end
115
+
116
+ end
@@ -0,0 +1,2 @@
1
+ module PlayersHelper
2
+ end
@@ -0,0 +1,42 @@
1
+ module ConcertoHardware
2
+ # The enigne's Ability class simply extends the existing Ability
3
+ # class for the application. We rely on the fact that it already
4
+ # includes CanCan::Ability.
5
+ class Ability < ::Ability
6
+ def initialize(accessor)
7
+ super # Get the main application's rules
8
+ # The main app will delegate to user_abilities, etc.
9
+ # Note the inherited rules give Admins rights to manage everything
10
+ end
11
+
12
+ def user_abilities(user)
13
+ super # Get the user rules from the main applications
14
+
15
+ # For debugging you may want to make all Players readable
16
+ # can :read, Player
17
+
18
+ # Let's defer to Concerto's rules for Screen Permissions.
19
+ # This will apply to both users browsing the hardware info
20
+ # as well as public screens in public instances accessing their data.
21
+ # - Right now, only admins can create players (TODO)
22
+ # - It may become desirable in the future for reading to be
23
+ # restricted or curtailed if a lot of sensitive data is stored here.
24
+ can :read, Player do |player|
25
+ !player.screen.nil? and can? :read, player.screen
26
+ end
27
+ can :update, Player do |player|
28
+ !player.screen.nil? and can? :update, player.screen
29
+ end
30
+ can :delete, Player do |player|
31
+ !player.screen.nil? and can? :delete, player.screen
32
+ end
33
+ end # user_abilities
34
+
35
+ def screen_abilities(screen)
36
+ # A logged-in screen can read its own Player information.
37
+ can :read, Player, :screen_id => screen.id
38
+ # In the future it may also need to write reporting data, so
39
+ # this will need to be expanded.
40
+ end # screen_abilities
41
+ end # class Ability
42
+ end # module ConcertoHardware
@@ -0,0 +1,169 @@
1
+ module ConcertoHardware
2
+ class Player < ActiveRecord::Base
3
+ belongs_to :screen
4
+
5
+ def self.time_accessor(*syms)
6
+ syms.each do |sym|
7
+ attr_accessor sym
8
+ composed_of sym,
9
+ :class_name => 'Time',
10
+ :mapping => [sym.to_s, "to_s"],
11
+ :constructor => Proc.new{ |item| item },
12
+ :converter => Proc.new{ |item| item }
13
+ end
14
+ end
15
+
16
+ # Hack to get the multiparameter virtual attributes working
17
+ def self.create_time_zone_conversion_attribute?(name, column)
18
+ column.nil? ? true : super
19
+ end
20
+
21
+ validates_associated :screen
22
+ validates_presence_of :screen_id
23
+ validates_presence_of :screen, :message => 'must exist'
24
+ validates_uniqueness_of :screen_id
25
+
26
+ time_accessor :wkday_on_time, :wkday_off_time
27
+ time_accessor :wknd_on_time, :wknd_off_time
28
+ time_accessor :party_time
29
+ attr_accessor :wknd_disable
30
+ attr_accessor :force_off
31
+
32
+ after_initialize :default_values
33
+ after_find :retrieve_screen_on_off
34
+ before_save :process_screen_on_off
35
+
36
+ def default_values
37
+ self.screen_on_off ||= [
38
+ {
39
+ :action => "on",
40
+ :wkday => "12345", # M-F
41
+ :time_after => "07:00",
42
+ :time_before => "20:00"
43
+ },
44
+ {
45
+ :action => "on",
46
+ :wkday => "06", # Sun, Sat
47
+ :time_after => "09:00",
48
+ :time_before => "20:00"
49
+ }
50
+ ].to_json
51
+ retrieve_screen_on_off #populate the virtual attributes.
52
+ end
53
+
54
+ # Take screen controls from the form and store them
55
+ # in a standard format that the player will understand.
56
+ # https://github.com/concerto/concerto-hardware/
57
+ # wiki/Player-API#screen-onoff-times
58
+ # TODO: Formatting for datetimes
59
+ # TODO: Validation
60
+ # TODO: TIMEZONES
61
+ def process_screen_on_off
62
+ ruleset = []
63
+ unless self.wkday_on_time.nil? or self.wkday_off_time.nil?
64
+ ruleset << {
65
+ :action => "on",
66
+ :wkday => "12345", # M-F
67
+ :time_after => fmt_time(wkday_on_time), # "07:00"
68
+ :time_before => fmt_time(wkday_off_time), # "23:00"
69
+ }
70
+ end
71
+ unless self.wknd_on_time.nil? or self.wknd_off_time.nil?
72
+ ruleset << {
73
+ :action => self.wknd_disable=="1" ? "off" : "on",
74
+ :wkday => "06", # Sun, Sat
75
+ :time_after => fmt_time(wknd_on_time), # "07:00"
76
+ :time_before => fmt_time(wknd_off_time), # "23:00"
77
+ }
78
+ end
79
+ if self.force_off == "1"
80
+ ruleset << {
81
+ :action => "off",
82
+ :date => Time.now.strftime("%Y-%m-%d")
83
+ }
84
+ end
85
+ if ruleset.empty? && self.screen_on_off.blank?
86
+ ruleset << {
87
+ :action => "on"
88
+ }
89
+ end
90
+
91
+ self.screen_on_off = ruleset.to_json unless ruleset.empty?
92
+ end
93
+
94
+ # This is a very limited parsing of the on/off rules
95
+ # Pretty much it will only work on rulesets created by the methods
96
+ # in this model. The format is very flexible, but this model only
97
+ # supports 3 simple rules.
98
+ def retrieve_screen_on_off
99
+ return nil if screen_on_off.blank?
100
+
101
+ ruleset = ActiveSupport::JSON.decode(self.screen_on_off)
102
+ ruleset.each do |rule|
103
+ if rule.has_key? 'action'
104
+ if rule.has_key? 'wkday' and rule['wkday']=='12345' and
105
+ rule['action']='on'
106
+ if rule.has_key? 'time_after' and rule.has_key? 'time_before'
107
+ self.wkday_on_time = Time.parse(rule['time_after'])
108
+ self.wkday_off_time = Time.parse(rule['time_before'])
109
+ end
110
+ end # weekday
111
+ if rule.has_key? 'wkday' and rule['wkday']=='06'
112
+ if rule.has_key? 'time_after' and rule.has_key? 'time_before'
113
+ self.wknd_on_time = Time.parse(rule['time_after'])
114
+ self.wknd_off_time = Time.parse(rule['time_before'])
115
+ end
116
+ self.wknd_disable = (rule['action'] != 'on')
117
+ end # weekend
118
+ if rule.has_key? 'date'
119
+ if rule['date'] == Time.now.strftime("%Y-%m-%d")
120
+ self.force_off = (rule['action'] != 'on')
121
+ end
122
+ end # force off rules
123
+ end # has an action
124
+ end # each rule
125
+ end # retrive_screen_on_off
126
+
127
+ # Relies on retrive_screen_on_off having been called at load.
128
+ def screen_on_off_valid
129
+ !(
130
+ wkday_on_time.nil? or wkday_off_time.nil? or
131
+ wknd_on_time.nil? or wknd_off_time.nil? or wknd_disable.nil?
132
+ )
133
+ end
134
+
135
+ # Returns an array of strings describing the screen's behavior.
136
+ # Relies on retrive_screen_on_off having been called at load.
137
+ def describe_screen_on_off
138
+ rules = []
139
+ if self.screen_on_off.blank?
140
+ rules << "On/off times not configured. The screen will always be on."
141
+ elsif !screen_on_off_valid
142
+ rules << "On/off rules are invalid. Edit and save the Player to fix."
143
+ else
144
+ rules << "Weekdays: on at "+fmt_time(wkday_on_time, "%l:%M%P")+", "+
145
+ "off at "+fmt_time(wkday_off_time, "%l:%M%P")+"."
146
+ if wknd_disable
147
+ rules << "Weekends: off."
148
+ else
149
+ rules << "Weekends: on at "+fmt_time(wknd_on_time, "%l:%M%P")+", "+
150
+ "off at "+fmt_time(wknd_off_time, "%l:%M%P")+"."
151
+ end
152
+ if force_off
153
+ rules << "Manual override: screen will be off until midnight tonight."
154
+ end
155
+ end
156
+ rules
157
+ end
158
+
159
+ def fmt_time(timeobj, fmt = "%H:%M")
160
+ if !timeobj.nil?
161
+ if timeobj.is_a?(String)
162
+ timeobj = Time.parse(timeobj)
163
+ end
164
+ timeobj.strftime(fmt).strip
165
+ end
166
+ end
167
+
168
+ end # class Player
169
+ end # module ConcertoHardware
@@ -0,0 +1,77 @@
1
+ <%= form_for(@player) do |f| %>
2
+ <%= render :partial => "layouts/errors", :object => @player %>
3
+
4
+ <fieldset>
5
+ <legend><span><%= t('.provide_details') %></span></legend>
6
+
7
+ <%= f.hidden_field :screen_id %>
8
+
9
+ <div class="clearfix">
10
+ <%= f.label :ip_address %>
11
+ <div class="input">
12
+ <%= f.text_field :ip_address %>
13
+ </div>
14
+ </div>
15
+
16
+ <div class="clearfix">
17
+ <div class="input checkbox">
18
+ <ul class="inputs-list">
19
+ <li><%= f.check_box :activated %> <%= f.label :activated %></li>
20
+ </ul>
21
+ </div>
22
+ </div>
23
+
24
+ <!-- Screen on/off times. Good luck, UI designer. -->
25
+ <div class="clearfix">
26
+ <%= f.label :wkday_on_time %>
27
+ <div class="input-prepend">
28
+ <span class="add-on"><%= t(:at) %></span>
29
+ <%= f.text_field(:wkday_on_time, :maxlength => 20, :class => "timefield input-small", :value => @player.fmt_time(@player.wkday_on_time, "%l:%M%P") || ConcertoConfig[:content_default_start_time]) %>
30
+ </div>
31
+ </div>
32
+ <div class="clearfix">
33
+ <%= f.label :wkday_off_time %>
34
+ <div class="input-prepend">
35
+ <span class="add-on"><%= t(:at) %></span>
36
+ <%= f.text_field(:wkday_off_time, :maxlength => 20, :class => "timefield input-small", :value => @player.fmt_time(@player.wkday_off_time, "%l:%M%P") || ConcertoConfig[:content_default_end_time]) %>
37
+ </div>
38
+ </div>
39
+
40
+ <div class="clearfix">
41
+ <div class="input checkbox">
42
+ <ul class="inputs-list">
43
+ <li><%= f.check_box :wknd_disable %> <%= f.label :wknd_disable %></li>
44
+ </ul>
45
+ </div>
46
+ </div>
47
+ <div class="clearfix">
48
+ <%= f.label :wknd_on_time %>
49
+ <div class="input-prepend">
50
+ <span class="add-on"><%= t(:at) %></span>
51
+ <%= f.text_field(:wknd_on_time, :maxlength => 20, :class => "timefield input-small", :value => @player.fmt_time(@player.wknd_on_time, "%l:%M%P") || ConcertoConfig[:content_default_start_time]) %>
52
+ </div>
53
+ </div>
54
+ <div class="clearfix">
55
+ <%= f.label :wknd_off_time %>
56
+ <div class="input-prepend">
57
+ <span class="add-on"><%= t(:at) %></span>
58
+ <%= f.text_field(:wknd_off_time, :maxlength => 20, :class => "timefield input-small", :value => @player.fmt_time(@player.wknd_off_time, "%l:%M%P") || ConcertoConfig[:content_default_end_time]) %>
59
+ </div>
60
+ </div>
61
+
62
+ <div class="clearfix">
63
+ <div class="input checkbox">
64
+ <ul class="inputs-list">
65
+ <li><%= f.check_box :force_off %> <%= f.label :force_off %> (temporarily turns off the screen until midnight tonight)</li>
66
+ </ul>
67
+ </div>
68
+ </div>
69
+ <p><em>All time fields should be entered in 24 hour format (HH:mm), for example 23:30 for 11:30 PM.</em></p>
70
+ </fieldset>
71
+
72
+ <div class="submit_bar actions">
73
+ <%= f.submit button_text, :class => "btn btn-primary" %>
74
+ <%= link_to t(:cancel), (@player.persisted? ? player_path : players_path), :class => "btn" %>
75
+ </div>
76
+
77
+ <% end %>
@@ -0,0 +1,45 @@
1
+ <div class="row-fluid">
2
+ <div class="span12">
3
+ <div class="default-padding">
4
+ <div class="subblock">
5
+ <div class="default-padding clearfix">
6
+
7
+ <p id="notice"><%= notice %></p>
8
+
9
+ <p>
10
+ <b>Ip address:</b>
11
+ <%= @player.ip_address %>
12
+ </p>
13
+
14
+ <p>
15
+ <b>Screen:</b>
16
+ <% if @player.screen.nil? %>
17
+ Not found
18
+ <% else %>
19
+ <% if can? :read, @player.screen %>
20
+ <%= link_to @player.screen.name, [main_app, @player.screen] %>
21
+ <% else %>
22
+ <%= @player.screen.name %>
23
+ <% end %>
24
+ <% end %>
25
+ </p>
26
+
27
+ <p>
28
+ <b>Activated:</b>
29
+ <%= @player.activated %>
30
+ </p>
31
+
32
+ <p>
33
+ <b>Screen on/off times:</b>
34
+ <ul>
35
+ <% @player.describe_screen_on_off.each do |rule| %>
36
+ <li><%=rule%></li>
37
+ <% end %>
38
+ </ul>
39
+ </p>
40
+
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ </div>
@@ -0,0 +1,17 @@
1
+ <div class="viewblock-header_right">
2
+ <div class="button-padding">
3
+ <% if can? :update, @player %>
4
+ <%= link_to t(:edit_model, :model => ConcertoHardware::Player.model_name.human), edit_player_path(@player), :class => "btn" %>
5
+ <% end %>
6
+ <% if can? :delete, @player %>
7
+ <%= link_to t(:destroy_model, :model => ConcertoHardware::Player.model_name.human), @player, :data => { :confirm => t(:are_you_sure_delete_model_key, :model => ConcertoHardware::Player.model_name.human, :key => @player.screen.name) }, :method => :delete, :class => "btn" %>
8
+ <% end %>
9
+ </div>
10
+ </div>
11
+
12
+ <div class="default-padding">
13
+ <h1>
14
+ <%= link_to t(:all_model, :model => ConcertoHardware::Player.model_name.human.pluralize), players_path %> &gt;
15
+ <%= @player.screen.name + " Player" %>
16
+ </h1>
17
+ </div>
@@ -0,0 +1,9 @@
1
+ <section class="viewblock">
2
+ <header class="viewblock-header">
3
+ <h1 class="default-padding"><%= t('.header', :screen => "#{@player.screen.name if !@player.screen.nil?}") %></h1>
4
+ </header>
5
+ <div class="viewblock-cont">
6
+ <%= render :partial => 'form', :locals => { :button_text => t(:update_model, :model => ConcertoHardware::Player.model_name.human) } %>
7
+ </div>
8
+ </section>
9
+
@@ -0,0 +1,49 @@
1
+ <section class="viewblock">
2
+ <header class="viewblock-header">
3
+ <div class="viewblock-header_right">
4
+ <div class="button-padding">
5
+ <% if can? :create, ConcertoHardware::Player %>
6
+ <%= link_to t(:new_model, :model => ConcertoHardware::Player.model_name.human), new_player_path, :class => "btn" %>
7
+ <% end %>
8
+ </div>
9
+ </div>
10
+ <h1 class="default-padding">
11
+ <%= t(:all_model, :model => ConcertoHardware::Player.model_name.human.pluralize) %>
12
+ </h1>
13
+ </header>
14
+ <div class="viewblock-cont">
15
+ <table>
16
+ <tr>
17
+ <th><%= ConcertoHardware::Player.human_attribute_name(:ip_address) %></th>
18
+ <th><%= ConcertoHardware::Player.human_attribute_name(:screen_id) %></th>
19
+ <th><%= ConcertoHardware::Player.human_attribute_name(:activated) %></th>
20
+ <th>Actions</th>
21
+ </tr>
22
+
23
+ <% @players.each do |player| %>
24
+ <tr>
25
+ <td><%= player.ip_address %></td>
26
+ <td><%= player.screen.name %></td>
27
+ <td><%= player.activated %></td>
28
+
29
+ <td>
30
+ <% if can? :read, player %>
31
+ <%= link_to t(:show, :model => ConcertoHardware::Player.model_name.human), player, :class => "btn" %>
32
+ <% end %>
33
+ <% if can? :edit, player %>
34
+ <%= link_to t(:edit, :model => ConcertoHardware::Player.model_name.human), edit_player_path(player), :class => "btn" %>
35
+ <% end %>
36
+ <% if can? :delete, player %>
37
+ <%= link_to t(:destroy, :model => ConcertoHardware::Player), player,
38
+ :data => { :confirm => t(:are_you_sure_delete_model_key, :model =>ConcertoHardware::Player.model_name.human, :key => player.screen.name) }, :method => :delete, :class => "btn" %>
39
+ <% end %>
40
+ </td>
41
+ </tr>
42
+ <% end %>
43
+ </table>
44
+ </div>
45
+ </section>
46
+ <div class="default-padding">
47
+ <%# for demo purposes: %>
48
+ Note: the poll interval is currently set to <%=ConcertoConfig[:poll_interval]%> seconds.
49
+ </div>
@@ -0,0 +1,8 @@
1
+ <section class="viewblock">
2
+ <header class="viewblock-header">
3
+ <h1 class="default-padding"><%= t('.header', :screen => "#{@player.screen.name if !@player.screen.nil?}") %></h1>
4
+ </header>
5
+ <div class="viewblock-cont">
6
+ <%= render :partial => 'form', :locals => { :button_text => t(:create_model, :model => ConcertoHardware::Player.model_name.human) } %>
7
+ </div>
8
+ </section>
@@ -0,0 +1,8 @@
1
+ <section class="viewblock">
2
+ <header class="viewblock-header" id="screens-header">
3
+ <%= render :partial => "show_header" %>
4
+ </header>
5
+ <div class="viewblock-cont">
6
+ <%= render :partial => "show_body" %>
7
+ </div>
8
+ </section>
@@ -0,0 +1,49 @@
1
+ <%# This partial will be read in the context of the main Concerto app. %>
2
+ <%# URL helpers require use of the 'hardware' routing helper. %>
3
+ <%# ConcertoHardware authorization rules are automatically provided. %>
4
+
5
+ <section class="viewblock">
6
+ <header class="viewblock-header">
7
+ <div class="viewblock-header_right">
8
+ <div class="button-padding">
9
+ <% if @player.nil? %>
10
+ <%= link_to 'Add Player', hardware.new_player_path(:screen_id => @screen.id), :class => "btn" %>
11
+ <% else %>
12
+ <% if can? :update, @player %>
13
+ <%= link_to 'Edit Player', hardware.edit_player_path(@player), :class => "btn" %>
14
+ <% end %>
15
+ <% end %>
16
+ </div>
17
+ </div>
18
+
19
+ <div class="default-padding">
20
+ <h1><%= t('.concerto_player') %></h1>
21
+ </div>
22
+ </header>
23
+ <div class="viewblock-cont">
24
+ <div class="default-padding">
25
+ <% if @player.nil? %>
26
+ <div class="alert alert-block alert-zero">
27
+ <p class="alert-heading"><%= t('.no_player_header') %></p>
28
+ <p><%= t('.no_player_msg') %></p>
29
+ <% if can? :update, @player %>
30
+ <%= link_to t('.add_player_msg'), hardware.new_player_path(:screen_id => @screen.id) %>
31
+ <% end %>
32
+ </div>
33
+ <% else %>
34
+ <h3><%= @screen.name + " Player" %></h3>
35
+ <p><b><%= @player.activated ? t('.activated') : t('.not_activated') %></b></p>
36
+ <br />
37
+ <% if can? :read, @player %>
38
+ <p><b>IP Address:</b> <%= @player.ip_address %></p>
39
+ <p><b>Screen On/Off Times:</b></p>
40
+ <ul>
41
+ <% @player.describe_screen_on_off.each do |rule| %>
42
+ <li><%= rule %></li>
43
+ <% end %>
44
+ </ul>
45
+ <% end %>
46
+ <% end %>
47
+ </div>
48
+ </div>
49
+ </section>
@@ -0,0 +1,29 @@
1
+ en:
2
+ activerecord:
3
+ models:
4
+ concerto_hardware/player: 'Player'
5
+ attributes:
6
+ concerto_hardware/player:
7
+ activated: 'Active'
8
+ ip_address: 'IP Address'
9
+ wkday_on_time: 'Weekday turn On time'
10
+ wkday_off_time: 'Weekday turn Off time'
11
+ wknd_disable: 'Disable on weekends'
12
+ wknd_on_time: 'Weekend turn On time'
13
+ wknd_off_time: 'Weekend turn Off time'
14
+
15
+ concerto_hardware:
16
+ players:
17
+ new:
18
+ header: 'New %{screen} Player'
19
+ edit:
20
+ header: 'Edit %{screen} Player'
21
+
22
+ screens:
23
+ screen_link:
24
+ concerto_player: "Concerto Player"
25
+ no_player_header: "No Player"
26
+ no_player_msg: "A Concerto Player has not yet been connected to this screen."
27
+ add_player_msg: "Specify a Player manually."
28
+ activated: "Activated"
29
+ not_activated: "Not Activated"
data/config/routes.rb ADDED
@@ -0,0 +1,21 @@
1
+ # The routing is configured such that it does its own routing.
2
+ # This means the central application will specifically mount
3
+ # the engine to make the routes available in a certain sub-url,
4
+ # for example one route would look like
5
+ # /hardware/players/new
6
+ ConcertoHardware::Engine.routes.draw do
7
+ # Just a test welcome page, we'll replace this with something
8
+ # more useful later.
9
+ root :to => proc { |env| [200, {}, ["Welcome to the hardware plugin!"]] }
10
+
11
+ # Since we have an isolated namespace, routes are automaticaly scoped
12
+ # to the ConcertoHardware module.
13
+ resources :players do
14
+ collection do
15
+ # Look up a player based on the screen ID.
16
+ match 'by_screen/:screen_id' => ConcertoHardware::PlayersController.action(:show)
17
+ # Show the player associated with the logged in screen.
18
+ match 'current' => ConcertoHardware::PlayersController.action(:show)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,12 @@
1
+ class CreateConcertoHardwarePlayers < ActiveRecord::Migration
2
+ def change
3
+ create_table :concerto_hardware_players do |t|
4
+ t.string :secret
5
+ t.string :ip_address
6
+ t.integer :screen_id
7
+ t.boolean :activated
8
+
9
+ t.timestamps
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,6 @@
1
+ class AddUpdatesToConcertoHardwarePlayers < ActiveRecord::Migration
2
+ def change
3
+ remove_column :concerto_hardware_players, :secret
4
+ add_column :concerto_hardware_players, :screen_on_off, :string
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ require "concerto_hardware/engine"
2
+
3
+ module ConcertoHardware
4
+ # There is no important functionality at the module level.
5
+ # Plugin configuration happens in engine.rb.
6
+ end
@@ -0,0 +1,68 @@
1
+ module ConcertoHardware
2
+ class Engine < ::Rails::Engine
3
+ # Isolated Namespace enforces healthy separation from the Concerto app.
4
+ # Nothing else should come before this call in this class.
5
+ isolate_namespace ConcertoHardware
6
+
7
+ # The engine name will be the name of the class
8
+ # that contains the URL helpers for our routes.
9
+ # This must come after isolate_namespace!
10
+ engine_name 'hardware'
11
+
12
+ # Define plugin information for the Concerto application to read.
13
+ # Do not modify @plugin_info outside of this static configuration block.
14
+ def plugin_info(plugin_info_class)
15
+ @plugin_info ||= plugin_info_class.new do
16
+ # Make the engine's controller accessible at /hardware
17
+ add_route("hardware", ConcertoHardware::Engine)
18
+
19
+ # Initialize configuration settings with a description and a default.
20
+ # Administrators can change the value through the Concerto dashboard.
21
+ add_config("poll_interval", "60",
22
+ :value_type => "integer",
23
+ :category => "System",
24
+ :seq_no => 999,
25
+ :description => "Client hardware polling interval in seconds")
26
+
27
+ # Some code to run at app boot
28
+ init do
29
+ Rails.logger.info "ConcertoHardware: Initialization code is running"
30
+ end
31
+
32
+ # The following hooks allow integration into the main Concerto app
33
+ # at the controller and view levels.
34
+
35
+ add_controller_hook "ScreensController", :show, :before do
36
+ @player = Player.find_by_screen_id(@screen.id)
37
+ end
38
+
39
+ add_controller_hook "ScreensController", :change, :before do
40
+ Rails.logger.info "concerto-hardware: screen change callback"
41
+ if @screen.auth_in_progress? # have a temp token to look at
42
+ if Player.where(:screen_id => @screen.id).count == 0 # No existing player
43
+ if ((@screen.temp_token.length > Screen::TEMP_TOKEN_LENGTH) and
44
+ (@screen.temp_token[Screen::TEMP_TOKEN_LENGTH].downcase == "s"))
45
+ # Okay, we have a legit player situation.
46
+ Rails.logger.info "concerto-hardware: creating Player for the new Screen!"
47
+ flash[:notice] ||= ""
48
+ player = Player.new
49
+ player.screen_id = @screen.id
50
+ player.activated = true
51
+ if player.save
52
+ Rails.logger.info " Success!"
53
+ #flash[:notice] << " A player hardware profile was automatically created!"
54
+ # TODO: User notification.
55
+ else
56
+ Rails.logger.info " Failed."
57
+ #flash[:notice] << " We could not create a player hardware profile, however."
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ add_view_hook "ScreensController", :screen_details, :partial => "concerto_hardware/screens/screen_link"
65
+ end
66
+ end
67
+ end # class Engine
68
+ end # module ConcertoHardware
@@ -0,0 +1,3 @@
1
+ module ConcertoHardware
2
+ VERSION = "0.0.5"
3
+ end
@@ -0,0 +1,13 @@
1
+ # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html
2
+
3
+ one:
4
+ secret: MyString
5
+ ip_address: MyString
6
+ screen_id: 1
7
+ activated: false
8
+
9
+ two:
10
+ secret: MyString
11
+ ip_address: MyString
12
+ screen_id: 1
13
+ activated: false
@@ -0,0 +1,49 @@
1
+ require 'test_helper'
2
+
3
+ class PlayersControllerTest < ActionController::TestCase
4
+ setup do
5
+ @player = players(:one)
6
+ end
7
+
8
+ test "should get index" do
9
+ get :index
10
+ assert_response :success
11
+ assert_not_nil assigns(:players)
12
+ end
13
+
14
+ test "should get new" do
15
+ get :new
16
+ assert_response :success
17
+ end
18
+
19
+ test "should create player" do
20
+ assert_difference('Player.count') do
21
+ post :create, player: @player.attributes
22
+ end
23
+
24
+ assert_redirected_to player_path(assigns(:player))
25
+ end
26
+
27
+ test "should show player" do
28
+ get :show, id: @player
29
+ assert_response :success
30
+ end
31
+
32
+ test "should get edit" do
33
+ get :edit, id: @player
34
+ assert_response :success
35
+ end
36
+
37
+ test "should update player" do
38
+ put :update, id: @player, player: @player.attributes
39
+ assert_redirected_to player_path(assigns(:player))
40
+ end
41
+
42
+ test "should destroy player" do
43
+ assert_difference('Player.count', -1) do
44
+ delete :destroy, id: @player
45
+ end
46
+
47
+ assert_redirected_to players_path
48
+ end
49
+ end
@@ -0,0 +1,4 @@
1
+ require 'test_helper'
2
+
3
+ class PlayersHelperTest < ActionView::TestCase
4
+ end
@@ -0,0 +1,7 @@
1
+ require 'test_helper'
2
+
3
+ class PlayerTest < ActiveSupport::TestCase
4
+ # test "the truth" do
5
+ # assert true
6
+ # end
7
+ end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: concerto_hardware
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5
5
+ platform: ruby
6
+ authors:
7
+ - Concerto Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 3.2.9
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 3.2.9
27
+ - !ruby/object:Gem::Dependency
28
+ name: sqlite3
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: A Rails Engine for managing Bandshell-powered Concerto hardware
42
+ email:
43
+ - team@concerto-signage.org
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - app/assets/images/concerto_hardware/helpicon.png
49
+ - app/assets/javascripts/concerto_hardware/application.js
50
+ - app/assets/javascripts/concerto_hardware/players.js
51
+ - app/assets/stylesheets/concerto_hardware/application.css
52
+ - app/assets/stylesheets/concerto_hardware/players.css.scss
53
+ - app/assets/stylesheets/scaffolds.css.scss
54
+ - app/controllers/concerto_hardware/application_controller.rb
55
+ - app/controllers/concerto_hardware/players_controller.rb
56
+ - app/helpers/players_helper.rb
57
+ - app/models/concerto_hardware/ability.rb
58
+ - app/models/concerto_hardware/player.rb
59
+ - app/views/concerto_hardware/players/_form.html.erb
60
+ - app/views/concerto_hardware/players/_show_body.html.erb
61
+ - app/views/concerto_hardware/players/_show_header.html.erb
62
+ - app/views/concerto_hardware/players/edit.html.erb
63
+ - app/views/concerto_hardware/players/index.html.erb
64
+ - app/views/concerto_hardware/players/new.html.erb
65
+ - app/views/concerto_hardware/players/show.html.erb
66
+ - app/views/concerto_hardware/screens/_screen_link.html.erb
67
+ - config/locales/en.yml
68
+ - config/routes.rb
69
+ - db/migrate/20121220000000_create_concerto_hardware_players.rb
70
+ - db/migrate/20131127201048_add_updates_to_concerto_hardware_players.rb
71
+ - lib/concerto-hardware.rb
72
+ - lib/concerto_hardware/engine.rb
73
+ - lib/concerto_hardware/version.rb
74
+ - LICENSE
75
+ - Rakefile
76
+ - README.rdoc
77
+ - test/fixtures/players.yml
78
+ - test/functional/players_controller_test.rb
79
+ - test/unit/helpers/players_helper_test.rb
80
+ - test/unit/player_test.rb
81
+ homepage: http://concerto-signage.org
82
+ licenses:
83
+ - Apache-2.0
84
+ metadata: {}
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubyforge_project:
101
+ rubygems_version: 2.0.14
102
+ signing_key:
103
+ specification_version: 4
104
+ summary: A Rails Engine for managing Bandshell-powered Concerto hardware
105
+ test_files:
106
+ - test/fixtures/players.yml
107
+ - test/functional/players_controller_test.rb
108
+ - test/unit/helpers/players_helper_test.rb
109
+ - test/unit/player_test.rb