rollout_admin 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.
Files changed (165) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +23 -0
  3. data/Rakefile +40 -0
  4. data/app/assets/images/rollout_admin/head_bg.jpg +0 -0
  5. data/app/assets/images/rollout_admin/icon_todos50x50.png +0 -0
  6. data/app/assets/images/rollout_admin/main_bg.jpg +0 -0
  7. data/app/assets/javascripts/rollout_admin/admin.js.erb +150 -0
  8. data/app/assets/javascripts/rollout_admin/application.js +19 -0
  9. data/app/assets/javascripts/rollout_admin/failures.js +2 -0
  10. data/app/assets/stylesheets/rollout_admin/admin.css +4 -0
  11. data/app/assets/stylesheets/rollout_admin/application.css +16 -0
  12. data/app/assets/stylesheets/rollout_admin/override.css.less +102 -0
  13. data/app/controllers/rollout_admin/admin_controller.rb +90 -0
  14. data/app/controllers/rollout_admin/application_controller.rb +4 -0
  15. data/app/helpers/rollout_admin/admin_helper.rb +4 -0
  16. data/app/helpers/rollout_admin/application_helper.rb +4 -0
  17. data/app/views/layouts/rollout_admin/application.html.erb +23 -0
  18. data/app/views/rollout_admin/admin/_feature.html.erb +71 -0
  19. data/app/views/rollout_admin/admin/_modals.html.erb +47 -0
  20. data/app/views/rollout_admin/admin/index.html.erb +26 -0
  21. data/config/routes.rb +12 -0
  22. data/lib/rollout_admin/engine.rb +5 -0
  23. data/lib/rollout_admin/version.rb +3 -0
  24. data/lib/rollout_admin.rb +4 -0
  25. data/lib/tasks/rollout_admin_tasks.rake +4 -0
  26. data/test/dummy/README.rdoc +261 -0
  27. data/test/dummy/Rakefile +7 -0
  28. data/test/dummy/app/assets/javascripts/application.js +15 -0
  29. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  30. data/test/dummy/app/controllers/application_controller.rb +3 -0
  31. data/test/dummy/app/helpers/application_helper.rb +2 -0
  32. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  33. data/test/dummy/config/application.rb +59 -0
  34. data/test/dummy/config/boot.rb +10 -0
  35. data/test/dummy/config/database.yml +25 -0
  36. data/test/dummy/config/environment.rb +5 -0
  37. data/test/dummy/config/environments/development.rb +37 -0
  38. data/test/dummy/config/environments/production.rb +67 -0
  39. data/test/dummy/config/environments/test.rb +37 -0
  40. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  41. data/test/dummy/config/initializers/inflections.rb +15 -0
  42. data/test/dummy/config/initializers/mime_types.rb +5 -0
  43. data/test/dummy/config/initializers/rollout.rb +2 -0
  44. data/test/dummy/config/initializers/secret_token.rb +7 -0
  45. data/test/dummy/config/initializers/session_store.rb +8 -0
  46. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  47. data/test/dummy/config/locales/en.yml +5 -0
  48. data/test/dummy/config/routes.rb +4 -0
  49. data/test/dummy/config.ru +4 -0
  50. data/test/dummy/db/development.sqlite3 +0 -0
  51. data/test/dummy/log/development.log +20262 -0
  52. data/test/dummy/public/404.html +26 -0
  53. data/test/dummy/public/422.html +26 -0
  54. data/test/dummy/public/500.html +25 -0
  55. data/test/dummy/public/favicon.ico +0 -0
  56. data/test/dummy/script/rails +6 -0
  57. data/test/dummy/tmp/cache/assets/C20/C40/sprockets%2F261100664832a5fd690af826f0851667 +0 -0
  58. data/test/dummy/tmp/cache/assets/C55/400/sprockets%2F9922ee592c403b460458680f1da79017 +0 -0
  59. data/test/dummy/tmp/cache/assets/C6D/6B0/sprockets%2F429143f762968712de61fb6c8629669e +0 -0
  60. data/test/dummy/tmp/cache/assets/C70/A20/sprockets%2F109d1361d6d5143160a288424a1b9ff3 +0 -0
  61. data/test/dummy/tmp/cache/assets/C78/460/sprockets%2F7d9501489510b06f2c36b455b1538e1b +0 -0
  62. data/test/dummy/tmp/cache/assets/C7F/680/sprockets%2F8d04822934a82a4e26599123a06fe17c +0 -0
  63. data/test/dummy/tmp/cache/assets/C8D/500/sprockets%2F2197959d63ad398e3abb7116574d7073 +0 -0
  64. data/test/dummy/tmp/cache/assets/C8E/370/sprockets%2F5715a23b738069dc623ff5d778259c70 +0 -0
  65. data/test/dummy/tmp/cache/assets/C96/930/sprockets%2F528e0632943be0f0d7c329f775a99688 +0 -0
  66. data/test/dummy/tmp/cache/assets/C9C/D90/sprockets%2F4a2d944b82b6411b4e3030cea0036896 +0 -0
  67. data/test/dummy/tmp/cache/assets/CA6/720/sprockets%2Fe54f120734cb3306547c49e23cd5c542 +0 -0
  68. data/test/dummy/tmp/cache/assets/CAB/130/sprockets%2Ff337a04f80ff3450c4103668a9064ad9 +0 -0
  69. data/test/dummy/tmp/cache/assets/CAE/F10/sprockets%2F16ff235a3919c04c0f0478414e59ca74 +0 -0
  70. data/test/dummy/tmp/cache/assets/CB3/100/sprockets%2F7187f0a20c6384eb48c29c70d008e388 +0 -0
  71. data/test/dummy/tmp/cache/assets/CB4/E00/sprockets%2F461896fc5136e1a54ca861480fb25f28 +0 -0
  72. data/test/dummy/tmp/cache/assets/CB5/DD0/sprockets%2Fc67208c4492b96c4332748c2c3e271ff +0 -0
  73. data/test/dummy/tmp/cache/assets/CB7/590/sprockets%2F764ce84c5b819b86fe552304ac814362 +0 -0
  74. data/test/dummy/tmp/cache/assets/CB9/6A0/sprockets%2Fd930e22f79e95b9b0331664d51666da6 +0 -0
  75. data/test/dummy/tmp/cache/assets/CBE/700/sprockets%2F00d72dab59c7e0a107102b8334b1e108 +0 -0
  76. data/test/dummy/tmp/cache/assets/CC1/1C0/sprockets%2Feb1b6997ba729596eb417627dc077345 +0 -0
  77. data/test/dummy/tmp/cache/assets/CC1/DA0/sprockets%2F675f039576d2fdc56653c7bc63d12875 +0 -0
  78. data/test/dummy/tmp/cache/assets/CC7/0C0/sprockets%2F7860733be5a69b995b7c9634530cf8e4 +0 -0
  79. data/test/dummy/tmp/cache/assets/CD2/3D0/sprockets%2F6fa3247d6904347a087bcb60ae4302f2 +0 -0
  80. data/test/dummy/tmp/cache/assets/CD2/B80/sprockets%2Ff31b41a931d6f0183c5cb0895483d21b +0 -0
  81. data/test/dummy/tmp/cache/assets/CD3/980/sprockets%2F03077d801d9c005f9a10f463f646c0fa +0 -0
  82. data/test/dummy/tmp/cache/assets/CD5/650/sprockets%2F3a67e7b7c24cb9f219112470c8f0304c +0 -0
  83. data/test/dummy/tmp/cache/assets/CD5/C00/sprockets%2F72b822081ea29032c5b7e7586bb5a22e +0 -0
  84. data/test/dummy/tmp/cache/assets/CD8/730/sprockets%2F32aabf38943204760722ca7e0c7fb288 +0 -0
  85. data/test/dummy/tmp/cache/assets/CDC/030/sprockets%2F5c09dc685263e63bdee05d5111a68525 +0 -0
  86. data/test/dummy/tmp/cache/assets/CDF/F20/sprockets%2Fb377780bb495f9308adc8f8c8040300a +0 -0
  87. data/test/dummy/tmp/cache/assets/CDF/FA0/sprockets%2Ff3348795f69c512ef32fce01d7006f00 +0 -0
  88. data/test/dummy/tmp/cache/assets/CEB/E40/sprockets%2Fdc0c77867a41f6512d992f5d380e429b +0 -0
  89. data/test/dummy/tmp/cache/assets/CEE/E80/sprockets%2F5a88f9906573e2187c61b4ce801e96ab +0 -0
  90. data/test/dummy/tmp/cache/assets/CF4/6A0/sprockets%2Fe14b6b684fe680f38f1166df49586b44 +0 -0
  91. data/test/dummy/tmp/cache/assets/CF8/220/sprockets%2F58cc7867e2797caf2081e9c8051c549f +0 -0
  92. data/test/dummy/tmp/cache/assets/D00/E70/sprockets%2F11823b27c41eed2102a188a853cbd8d7 +0 -0
  93. data/test/dummy/tmp/cache/assets/D04/190/sprockets%2F462b137adb8c4e37522d854b6a2eb730 +0 -0
  94. data/test/dummy/tmp/cache/assets/D06/ED0/sprockets%2F708d4ef91c962e6cb60012b0e2c53f25 +0 -0
  95. data/test/dummy/tmp/cache/assets/D0D/CA0/sprockets%2Fbb60dc938786ab2a91b28706cb32a298 +0 -0
  96. data/test/dummy/tmp/cache/assets/D0E/380/sprockets%2F4b12d7f74747f23c6017a4a7e965ab3c +0 -0
  97. data/test/dummy/tmp/cache/assets/D0E/760/sprockets%2Ffe56acc145c40e203265082f584e7d7e +0 -0
  98. data/test/dummy/tmp/cache/assets/D0E/ED0/sprockets%2F9261ee78b6d57ac5fe6171cb17114b61 +0 -0
  99. data/test/dummy/tmp/cache/assets/D10/C50/sprockets%2F6075c7bc4cc3b2d04d54bbc934709697 +0 -0
  100. data/test/dummy/tmp/cache/assets/D13/6F0/sprockets%2Fb12b479efabdb654778797e40044fa31 +0 -0
  101. data/test/dummy/tmp/cache/assets/D16/B20/sprockets%2F3e87067a172937368f3ddfdabcd14445 +0 -0
  102. data/test/dummy/tmp/cache/assets/D1F/440/sprockets%2Fd3862d1d68f97615f146311eab7d7f9d +0 -0
  103. data/test/dummy/tmp/cache/assets/D24/D00/sprockets%2F76e4073a159f5418f55ee35ca93f97ec +0 -0
  104. data/test/dummy/tmp/cache/assets/D28/0F0/sprockets%2Ff220f24f7de79602db8669ca8a18997f +0 -0
  105. data/test/dummy/tmp/cache/assets/D2C/D90/sprockets%2F3ec24ca7b474681701e355aa229bca3b +0 -0
  106. data/test/dummy/tmp/cache/assets/D32/1F0/sprockets%2Fe1af7c212b842ba6186afa058252c9f2 +0 -0
  107. data/test/dummy/tmp/cache/assets/D33/B80/sprockets%2F1c3371fbdb4f59fd006cb903018d257c +0 -0
  108. data/test/dummy/tmp/cache/assets/D3A/300/sprockets%2Ffaa86f001fde572c3be445509308c3b9 +0 -0
  109. data/test/dummy/tmp/cache/assets/D3E/340/sprockets%2Fc62625e05a6ab78fa7108bc91219ff6c +0 -0
  110. data/test/dummy/tmp/cache/assets/D42/130/sprockets%2F0ce4f38cdd901f2fb060876f217492df +0 -0
  111. data/test/dummy/tmp/cache/assets/D43/A00/sprockets%2F0af849366f9470e63fc22ec28ecc9a00 +0 -0
  112. data/test/dummy/tmp/cache/assets/D44/E80/sprockets%2F66ae491aae5c0e18e599d45e22e552b6 +0 -0
  113. data/test/dummy/tmp/cache/assets/D45/270/sprockets%2Fa9573efccd81ee042694caf103663e85 +0 -0
  114. data/test/dummy/tmp/cache/assets/D48/3F0/sprockets%2Fc73b9787cfd00e9ef343185e511fc3e3 +0 -0
  115. data/test/dummy/tmp/cache/assets/D4F/160/sprockets%2Fa05fd4653009b8a45acb40f0ad2d412b +0 -0
  116. data/test/dummy/tmp/cache/assets/D4F/B50/sprockets%2F70467bdfb28b8966960e3d0c589e2add +0 -0
  117. data/test/dummy/tmp/cache/assets/D55/3C0/sprockets%2F5a0966ba1bff69a36fe2f39473f9b399 +0 -0
  118. data/test/dummy/tmp/cache/assets/D6B/250/sprockets%2Fedf4f5f302a722e3fd6f6509b3ed1810 +0 -0
  119. data/test/dummy/tmp/cache/assets/D6B/B30/sprockets%2Fb7cf44c5e1ea68800d9e3a9235ae5a60 +0 -0
  120. data/test/dummy/tmp/cache/assets/D6C/0C0/sprockets%2F1ac2e63a2b7fd76b515eb5d95b2856a5 +0 -0
  121. data/test/dummy/tmp/cache/assets/D6C/B30/sprockets%2F1d5ec72e04cd10fb8c90bbda87088817 +0 -0
  122. data/test/dummy/tmp/cache/assets/D6D/870/sprockets%2Fee06e0b902601c67d24c9fb7a3cbe696 +0 -0
  123. data/test/dummy/tmp/cache/assets/D71/C20/sprockets%2Fc7303b23dce837fa7b76f4125fc4e46e +0 -0
  124. data/test/dummy/tmp/cache/assets/D71/FF0/sprockets%2Fc57f0246fc0b8ee64dc164f34a52c6d9 +0 -0
  125. data/test/dummy/tmp/cache/assets/D72/7C0/sprockets%2Fef5c15a386af59791adc70444adc93d2 +0 -0
  126. data/test/dummy/tmp/cache/assets/D74/A80/sprockets%2F18ba650d0ac50053fe8cf97f9db98f31 +0 -0
  127. data/test/dummy/tmp/cache/assets/D7A/530/sprockets%2Fa45e2fe38b2980a24c715997edfbb3e7 +0 -0
  128. data/test/dummy/tmp/cache/assets/D83/AC0/sprockets%2F784c9ffae2dd6a84e91293a8b078cd83 +0 -0
  129. data/test/dummy/tmp/cache/assets/D90/660/sprockets%2F1cc7ddd5f049da1ab735a137e7424b3c +0 -0
  130. data/test/dummy/tmp/cache/assets/D97/3F0/sprockets%2F73b6a9e91e1c3dfbde7df506063f030a +0 -0
  131. data/test/dummy/tmp/cache/assets/D9A/F70/sprockets%2F9b3298a5ca38cbfb3fb9a062ee36b180 +0 -0
  132. data/test/dummy/tmp/cache/assets/DA1/B70/sprockets%2F6fd2228ab269ca977b97aab086c60fda +0 -0
  133. data/test/dummy/tmp/cache/assets/DA2/090/sprockets%2F472d4241ad9f4b6e791f5f8dfd31ce0a +0 -0
  134. data/test/dummy/tmp/cache/assets/DA2/BB0/sprockets%2F20dc7d35189b5b8b2de1d4d072e9ff7c +0 -0
  135. data/test/dummy/tmp/cache/assets/DA6/C70/sprockets%2F78df72bc3eb5d94abf5f652cb20708e7 +0 -0
  136. data/test/dummy/tmp/cache/assets/DA6/F90/sprockets%2Fe75e1ebb6089a1862bfdb14588f8d3ac +0 -0
  137. data/test/dummy/tmp/cache/assets/DAA/420/sprockets%2Ff4b2a98efc26e6289263cc0d3bd79c4e +0 -0
  138. data/test/dummy/tmp/cache/assets/DAC/F50/sprockets%2Faf5a558648cd7bc81e5b85df6fa125d7 +0 -0
  139. data/test/dummy/tmp/cache/assets/DB2/0A0/sprockets%2Faeac771697881809329b2ebeeced8bf6 +0 -0
  140. data/test/dummy/tmp/cache/assets/DBD/330/sprockets%2F51a9cf8afaaa9b816cb8a70ac7512a21 +0 -0
  141. data/test/dummy/tmp/cache/assets/DBF/B40/sprockets%2Fe00c05fe811a5201ecf81e1af8e6eca8 +0 -0
  142. data/test/dummy/tmp/cache/assets/DC1/AB0/sprockets%2F2e20ea6c418db23cb9c2cf1782a95cad +0 -0
  143. data/test/dummy/tmp/cache/assets/DCB/240/sprockets%2F23b7eecc9a7c0c38f7c5316efaa60e17 +0 -0
  144. data/test/dummy/tmp/cache/assets/DD2/1E0/sprockets%2F9ead9f72b0d11cb7a9f5637f77ab13be +0 -0
  145. data/test/dummy/tmp/cache/assets/DD9/300/sprockets%2Fa24d5e2b2b38e7faf91cf3738d6f48ec +0 -0
  146. data/test/dummy/tmp/cache/assets/DDB/AA0/sprockets%2Ff9b53bed9958305cff6ff8dc0f330bd2 +0 -0
  147. data/test/dummy/tmp/cache/assets/DDC/990/sprockets%2F4a1cef3fbac3d74d9a15d3997ed9918c +0 -0
  148. data/test/dummy/tmp/cache/assets/DDD/E70/sprockets%2F0b0e2c06badeabd4ea9b52178b44a00e +0 -0
  149. data/test/dummy/tmp/cache/assets/DEF/610/sprockets%2Fe23e6b6dadeb8f01ab452bab578a532e +0 -0
  150. data/test/dummy/tmp/cache/assets/DF0/D30/sprockets%2F88ab3631daac054acf95528ce1acc4cd +0 -0
  151. data/test/dummy/tmp/cache/assets/DF4/4C0/sprockets%2Fa722b8a28bce4d3346bd0be5f46bdae7 +0 -0
  152. data/test/dummy/tmp/cache/assets/DFB/360/sprockets%2Fbe14b4380ce7db7ec289dbe3d4cfa092 +0 -0
  153. data/test/dummy/tmp/cache/assets/DFB/570/sprockets%2F15ee84ca369789a1c4adf2cc0a2ffea4 +0 -0
  154. data/test/dummy/tmp/cache/assets/DFC/E40/sprockets%2F8fde3a1113aeecd77fb2f27c0e276be8 +0 -0
  155. data/test/dummy/tmp/cache/assets/E0B/DF0/sprockets%2Fd1a8c47b74aa53edbb5288992ddedd9f +0 -0
  156. data/test/dummy/tmp/cache/assets/E33/010/sprockets%2Ffd7db48da51f060c6bdd80dfbd992d8b +0 -0
  157. data/test/dummy/tmp/cache/assets/E33/050/sprockets%2Fe5ce222fae640d1bf9f3fc96eaa87fb4 +0 -0
  158. data/test/dummy/tmp/cache/assets/E3D/4A0/sprockets%2F6954eff7c65c4d1fc8d8571aeecaafd3 +0 -0
  159. data/test/dummy/tmp/cache/assets/EBF/FA0/sprockets%2Fafa901c76afcff908decda3eea27b9be +0 -0
  160. data/test/functional/rollout_admin/admin_controller_test.rb +11 -0
  161. data/test/integration/navigation_test.rb +10 -0
  162. data/test/rollout_admin_test.rb +7 -0
  163. data/test/test_helper.rb +15 -0
  164. data/test/unit/helpers/rollout_admin/admin_helper_test.rb +6 -0
  165. metadata +444 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 Alexander Balsam
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,23 @@
1
+ = RolloutAdmin
2
+
3
+ Gem to controll your rollout features via a webinterface. This is the first "not yet clean" version. Please do not use in production environment.
4
+
5
+ == INSTALL
6
+
7
+ Add rollout_admin to your Gemfile. Then bundle install. Make sure you have rollout installed as well from nedeco git because rollout_admin relies on the IP address extension.
8
+
9
+ rollout_admin expects that you have a User model with the attributes "id", "name", "email". Rollout must be initialized and available viw $rollout variable.
10
+
11
+ Add the following line to your routes.rb:
12
+
13
+ mount RolloutAdmin::Engine => "/rollout_admin"
14
+
15
+ You will be asked for a username and password. This is "foo/bar".
16
+
17
+ == TODO
18
+
19
+ - check dependencies
20
+ - make detailed install instructions
21
+ - add configuration for authentication
22
+ - make ip feature configurable
23
+ - make User model dependency more generic
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 = 'RolloutAdmin'
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,150 @@
1
+ <%
2
+ # import the routes from the engine router to be aware
3
+ # of the mount path when doing javascript ajax calls
4
+ url = RolloutAdmin::Engine.routes.url_helpers
5
+ %>
6
+
7
+ $(function(){
8
+ $('a.icon-chevron-down').click(
9
+ function(){
10
+ $(this).parent().find('div.details').fadeIn(1000);
11
+ $(this).hide();
12
+ $(this).parent().find('a.icon-chevron-up').show();
13
+ }
14
+ );
15
+ $('a.icon-chevron-up').click(
16
+ function(){
17
+ $(this).parent().find('div.details').fadeOut(500);
18
+ $(this).hide();
19
+ $(this).parent().find('a.icon-chevron-down').show();
20
+ }
21
+ );
22
+
23
+ $(document).delegate('i.delete_user', 'click',
24
+ function(){
25
+ var context=$(this).closest("div").parent().find("h4#feature_name").html(),
26
+ that=$(this).closest("li"),
27
+ ulist=$(this).closest("ul");
28
+ $.post('<%= url.remove_path(:format => :json )%>', {object_type:"user", user: $(this).closest("li").text(), feature: context}, function(data) {
29
+ that.remove();
30
+ if ($("li",ulist).length == 1) {
31
+ ulist.children(':last').before('<li>No members</li>');
32
+ }
33
+ });
34
+ });
35
+
36
+ $(document).delegate('i.delete_group','click',
37
+ function(){
38
+ var context=$(this).closest("div").parent().find("h4#feature_name").html(),
39
+ that=$(this).closest("li"),
40
+ ulist=$(this).closest("ul");
41
+ $.post('<%= url.remove_path(:format => :json )%>', {object_type:"group", group: $(this).closest("li").text(), feature: context}, function(data) {
42
+ that.remove();
43
+ if ($("li",ulist).length == 1) {
44
+ ulist.children(':last').before('<li>No members</li>');
45
+ }
46
+ });
47
+ });
48
+
49
+ $(document).delegate('i.delete_ip','click',
50
+ function(){
51
+ var context=$(this).closest("div").parent().find("h4#feature_name").html(),
52
+ that=$(this).closest("li"),
53
+ ulist=$(this).closest("ul");
54
+ $.post('<%= url.remove_path(:format => :json )%>', {object_type:"ip", ip: $(this).closest("li").text(), feature: context}, function(data) {
55
+ that.remove();
56
+ if ($("li",ulist).length == 1) {
57
+ ulist.children(':last').before('<li>No members</li>');
58
+ }
59
+ });
60
+ });
61
+
62
+
63
+ $('i.add_users').click(
64
+ function(){
65
+ context=$(this).closest("div").parent().find("h4#feature_name").html();
66
+ $('.feature_name').html(context);
67
+ $that = $(this);
68
+
69
+ $.getJSON('<%= url.get_users_path(:format => :json )%>', function(json) {
70
+ userlist='<h5>Select users to add</h5><form id="userlist"><ul class="unstyled">';
71
+ $.each(json, function() {
72
+ userlist += '<li><input type="checkbox", value="'+this.id+'" data-username="'+this.Name+'"> '+this.Name+', '+this.email+'</li>';
73
+ });
74
+ userlist +='</ul></form>';
75
+
76
+ $('#addUserModal').find("div.modal-body").html(userlist);
77
+
78
+ // clear former event handlers
79
+ $('#addUserModal').find("div.modal-footer").find("button.btn-primary").unbind();
80
+ $('#addUserModal').find("div.modal-footer").find("button.btn-primary").click(function() {
81
+ var allUsers = [];
82
+ var allUserNames = [];
83
+ $('#userlist :checked').each(function() {
84
+ allUsers.push($(this).val());
85
+ allUserNames.push($(this).data("username"));
86
+ });
87
+
88
+ $.post('<%= url.add_path(:format => :json )%>', {object_type:"user", user: allUsers.join(), feature: context}, function(data) {
89
+ list = $that.closest('ul');
90
+ if (list.children(':first').text() == "No members") {
91
+ list.children(':first').remove();
92
+ }
93
+ allUserNames.forEach(function(entry) {
94
+ list.children(':last').before('<li><i class="icon-minus delete_user"></i>'+entry+'</li>');
95
+ });
96
+ });
97
+ $('#addUserModal').modal('hide');
98
+ });
99
+ $('#addUserModal').modal();
100
+
101
+ });
102
+
103
+ }
104
+ );
105
+ $('i.add_groups').click(
106
+ function(){
107
+ context=$(this).closest("div").parent().find("h4#feature_name").html();
108
+ $('.feature_name').html(context);
109
+ $that = $(this);
110
+
111
+ // clear former event handlers
112
+ $('#addGroupModal').find("div.modal-footer").find("button.btn-primary").unbind();
113
+ $('#addGroupModal').find("div.modal-footer").find("button.btn-primary").click(function() {
114
+ $.post('<%= url.add_path(:format => :json )%>', {object_type:"group", group: $('#addGroupModal').find("div.modal-body").find("input#group_name").val(), feature: context}, function(data) {
115
+ list = $that.closest('ul');
116
+ if (list.children(':first').text() == "No members") {
117
+ list.children(':first').remove();
118
+ }
119
+ list.children(':last').before('<li><i class="icon-minus delete_group"></i>'+$('#addGroupModal').find("div.modal-body").find("input#group_name").val()+'</li>');
120
+ $('#addGroupModal').find("div.modal-body").find("input#group_name").val('');
121
+ });
122
+ $('#addGroupModal').modal('hide');
123
+ });
124
+ $('#addGroupModal').modal();
125
+ }
126
+ );
127
+ $('i.add_ips').click(
128
+ function(){
129
+ context=$(this).closest("div").parent().find("h4#feature_name").html();
130
+ $('.feature_name').html(context);
131
+ $that = $(this);
132
+
133
+ // clear former event handlers
134
+ $('#addIPModal').find("div.modal-footer").find("button.btn-primary").unbind();
135
+ $('#addIPModal').find("div.modal-footer").find("button.btn-primary").click(function() {
136
+ $.post('<%= url.add_path(:format => :json )%>', {object_type:"ip", ip: $('#addIPModal').find("div.modal-body").find("input#ipaddr").val(), feature: context}, function(data) {
137
+
138
+ list = $that.closest('ul');
139
+ if (list.children(':first').text() == "No members") {
140
+ list.children(':first').remove();
141
+ }
142
+ list.children(':last').before('<li><i class="icon-minus delete_ip"></i>'+$('#addIPModal').find("div.modal-body").find("input#ipaddr").val()+'</li>');
143
+ $('#addIPModal').find("div.modal-body").find("input#ipaddr").val('');
144
+ });
145
+ $('#addIPModal').modal('hide');
146
+ });
147
+ $('#addIPModal').modal();
148
+ }
149
+ );
150
+ });
@@ -0,0 +1,19 @@
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 jquery.ui.slider
16
+ //= require jquery.ui.dialog
17
+ //= require jquery.ui.datepicker
18
+ //= require twitter/bootstrap
19
+ //= 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,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -0,0 +1,16 @@
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 twitter/bootstrap
12
+ *= require_self
13
+ *= require jquery.ui.slider
14
+ *= require jquery.ui.datepicker
15
+ *= require_tree .
16
+ */
@@ -0,0 +1,102 @@
1
+
2
+ @import "twitter/bootstrap/variables";
3
+ @import "twitter/bootstrap/mixins";
4
+ @import "twitter/bootstrap/responsive";
5
+
6
+
7
+ /* clearfix */
8
+ .cf{
9
+ zoom:1;
10
+ &:before,
11
+ &:after{
12
+ content:"";
13
+ display:table;
14
+ }
15
+ &:after{
16
+ clear:both;
17
+ }
18
+ }
19
+
20
+ html, body {
21
+ background: url(/assets/rollout_admin/main_bg.jpg);
22
+ font-family: 'Roboto Condensed', sans-serif;
23
+ font-size: 12px;
24
+ color: @white;
25
+ }
26
+
27
+ header {
28
+ z-index: 100;
29
+ position: fixed;
30
+ display: block;
31
+ height: 60px;
32
+ box-shadow: 0 0 30px @darkblue;
33
+ background: url(/assets/rollout_admin/head_bg.jpg);
34
+ border-bottom: 5px solid @blue;
35
+ width: 100%;
36
+
37
+ @media (max-width: 767px) {
38
+ margin-left: -20px;
39
+ margin-right: -20px;
40
+ }
41
+
42
+ .app_icon {
43
+ margin: 6px;
44
+ cursor: pointer;
45
+ }
46
+ a.addlist i {
47
+ margin: 18px 10px;
48
+ }
49
+ }
50
+
51
+ .title {
52
+ color: #fff;
53
+ }
54
+
55
+ div.container{
56
+ padding-top: 70px;
57
+ }
58
+
59
+
60
+ .transition (@time: 0.2s) {
61
+ transition: all @time;
62
+ -moz-transition: all @time; /* Firefox 4 */
63
+ -webkit-transition: all @time; /* Safari and Chrome */
64
+ -o-transition: all @time; /* Opera */
65
+ }
66
+
67
+ .corners (@radius: 5px) {
68
+ -webkit-border-radius: @radius;
69
+ -moz-border-radius: @radius;
70
+ -ms-border-radius: @radius;
71
+ -o-border-radius: @radius;
72
+ border-radius: @radius;
73
+ }
74
+
75
+
76
+ @darkblue: #04263a;
77
+
78
+
79
+ div.feature {
80
+ color: #111;
81
+ background-color: #eee;
82
+ margin-bottom: 20px;
83
+ box-shadow: 0 5px 20px #000;
84
+
85
+ padding: 10px;
86
+ border: solid 1px #ccc;
87
+ .box-sizing(border-box);
88
+ .border-radius( 5px );
89
+ .actions {
90
+ position:relative;
91
+ bottom: 25px;
92
+ float: right;
93
+ }
94
+ .details {
95
+ display:none;
96
+ }
97
+ }
98
+
99
+ div.modal {
100
+ color: #111;
101
+ }
102
+
@@ -0,0 +1,90 @@
1
+ require_dependency "rollout_admin/application_controller"
2
+
3
+ module RolloutAdmin
4
+ class AdminController < ApplicationController
5
+
6
+ before_filter :authenticate
7
+
8
+ def index
9
+ @feature_list = $rollout.features
10
+ @features = []
11
+ @feature_list.uniq.each { |feature|
12
+ @features << $rollout.get(feature)
13
+ }
14
+ end
15
+
16
+ def create
17
+ $rollout.activate_percentage(params[:feature_name], 0)
18
+ redirect_to index_path
19
+ end
20
+
21
+ def deactivate
22
+ if params[:object_type] == "feature"
23
+ $rollout.deactivate(params[:object].to_s)
24
+ end
25
+ redirect_to index_path
26
+ end
27
+
28
+ def activate
29
+ if params[:object_type] == "feature"
30
+ $rollout.activate(params[:object].to_s)
31
+ end
32
+ redirect_to index_path
33
+ end
34
+
35
+ def get_users
36
+ @users=User.all
37
+ render :json => @users
38
+ end
39
+
40
+ def add
41
+ if params[:object_type] == "user"
42
+ $rollout.activate_user(params[:feature], User.find(params[:user].to_i))
43
+ @feature = $rollout.get(params[:feature])
44
+ render :json => @feature.users
45
+ elsif params[:object_type] == "group"
46
+ $rollout.activate_group(params[:feature], params[:group].to_sym)
47
+ @feature = $rollout.get(params[:feature])
48
+ render :json => @feature.groups
49
+ elsif params[:object_type] == "ip"
50
+ $rollout.activate_ip(params[:feature], params[:ip].to_s)
51
+ @feature = $rollout.get(params[:feature])
52
+ render :json => @feature.ips
53
+ end
54
+ end
55
+
56
+ def remove
57
+ if params[:object_type] == "user"
58
+ @user=User.where(:name => params[:user]).first
59
+ if @user
60
+ $rollout.deactivate_user(params[:feature], @user)
61
+ end
62
+ @feature = $rollout.get(params[:feature])
63
+ render :json => @feature.users
64
+ elsif params[:object_type] == "group"
65
+ $rollout.deactivate_group(params[:feature], params[:group].to_sym)
66
+ @feature = $rollout.get(params[:feature])
67
+ render :json => @feature.groups
68
+ elsif params[:object_type] == "ip"
69
+ puts params[:ip]
70
+ $rollout.deactivate_ip(params[:feature], params[:ip].to_s)
71
+ @feature = $rollout.get(params[:feature])
72
+ render :json => @feature.ips
73
+ end
74
+ end
75
+
76
+ def update_percentage
77
+ $rollout.activate_percentage(params[:feature].to_sym,params[:percentage].to_i)
78
+ render :json => {:status => "success"}
79
+ end
80
+
81
+ protected
82
+
83
+ def authenticate
84
+ authenticate_or_request_with_http_basic do |username, password|
85
+ username == "foo" && password == "bar"
86
+ end
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,4 @@
1
+ module RolloutAdmin
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module RolloutAdmin
2
+ module AdminHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module RolloutAdmin
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,23 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>RolloutAdmin</title>
5
+ <%= stylesheet_link_tag "rollout_admin/application", :media => "all" %>
6
+ <%= javascript_include_tag "rollout_admin/application" %>
7
+ <link href='http://fonts.googleapis.com/css?family=Roboto+Condensed' rel='stylesheet' type='text/css'>
8
+
9
+ <%= csrf_meta_tags %>
10
+ </head>
11
+ <body>
12
+ <header>
13
+ <div class="dropdown" style="float:left;">
14
+ <%= image_tag "rollout_admin/icon_todos50x50.png", :class => "app_icon", :style => "float:left; margin-right: 20px;" %>
15
+ <h1 style="float:left;">Feature Administration</h1>
16
+ </div>
17
+ </header>
18
+
19
+ <div class="container">
20
+ <%= yield %>
21
+ </div>
22
+ </body>
23
+ </html>
@@ -0,0 +1,71 @@
1
+ <div class="feature">
2
+ <h4 id="feature_name"><%=feature.name%></h4>
3
+ <a class="icon-chevron-down"></a>
4
+ <a class="icon-chevron-up" style="display:none;"></a>
5
+ <%#=$rollout.get(feature).inspect%>
6
+ <div id="details" class="details">
7
+ <dl class="dl-horizontal">
8
+ <dt>Users</dt>
9
+ <dd>
10
+ <ul class="unstyled">
11
+ <% if feature.users.count > 0 %>
12
+ <% feature.users.each {|user| %>
13
+ <li><i class="icon-minus delete_user"></i><%=User.find(user.to_i).Name%></li>
14
+ <% } %>
15
+ <% else %>
16
+ <li>No members</li>
17
+ <% end %>
18
+ <li><i class="icon-plus add_users"></i></li>
19
+ </ul>
20
+ </dd>
21
+ <dt>Groups</dt>
22
+ <dd>
23
+ <ul class="unstyled">
24
+ <% if feature.groups.count > 0 %>
25
+ <% feature.groups.each {|group| %>
26
+ <li><i class="icon-minus delete_group"></i><%=group%></li>
27
+ <% } %>
28
+ <% else %>
29
+ <li>No members</li>
30
+ <% end %>
31
+ <li><i class="icon-plus add_groups"></i></li>
32
+ </ul>
33
+ </dd>
34
+ <dt>IPs</dt>
35
+ <dd>
36
+ <ul class="unstyled">
37
+ <% if feature.ips.count > 0 %>
38
+ <% feature.ips.each {|ip| %>
39
+ <li><i class="icon-minus delete_ip"></i><%=ip%></li>
40
+ <% } %>
41
+ <% else %>
42
+ <li>No members</li>
43
+ <% end %>
44
+ <li><i class="icon-plus add_ips"></i></li>
45
+ </ul>
46
+ </dd>
47
+ <dt>Percentage</dt>
48
+ <dd>
49
+ <div id="slider_<%=feature.name%>" style="width: 100%;"></div>
50
+ <p class="slider-input" style="width: 100%; text-align:center;"><%=feature.percentage%>%</p>
51
+ </dd>
52
+ </dl>
53
+ </div>
54
+ <div class="btn-group actions">
55
+ <%= link_to "activate global", "/rollout_admin/activate?object_type=feature&object="+ feature.name.to_s, :method => :post, :class => "btn"%>
56
+
57
+ <%= link_to "deactivate global", "/rollout_admin/deactivate?object_type=feature&object="+ feature.name.to_s, :method => :post, :class => "btn"%>
58
+ </div>
59
+ </div>
60
+ <script>
61
+ $("#slider_<%=feature.name%>").slider({
62
+ value: <%=feature.percentage%>,
63
+ 'slide': function(e, ui){
64
+ console.log($(this).siblings('.slider-input'));
65
+ $(this).siblings('.slider-input').html(ui.value + "%");
66
+ $.post('<%= update_percentage_path(:format => :json )%>', {percentage: ui.value, feature: "<%=feature.name%>"}, function(data) {
67
+
68
+ });
69
+ }
70
+ });
71
+ </script>
@@ -0,0 +1,47 @@
1
+ <!-- addUserModal-->
2
+ <div id="addUserModal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
3
+ <div class="modal-header">
4
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
5
+ <h3 id="myModalLabel">Add User to <p class="feature_name"></p></h3>
6
+ </div>
7
+ <div class="modal-body">
8
+ <p>here we will display all users</p>
9
+ </div>
10
+ <div class="modal-footer">
11
+ <button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
12
+ <button class="btn btn-primary">Save changes</button>
13
+ </div>
14
+ </div>
15
+
16
+ <!-- addGroupModal-->
17
+ <div id="addGroupModal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
18
+ <div class="modal-header">
19
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
20
+ <h3 id="myModalLabel">Add Group to <p class="feature_name"></p></h3>
21
+ </div>
22
+ <div class="modal-body">
23
+ <p>Group name</p>
24
+ <input type="text" name="group_name" id="group_name">
25
+ <p>Please type in the name of a group you would like to add.</p>
26
+ </div>
27
+ <div class="modal-footer">
28
+ <button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
29
+ <button class="btn btn-primary">Save changes</button>
30
+ </div>
31
+ </div>
32
+
33
+ <!-- addIPModal-->
34
+ <div id="addIPModal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
35
+ <div class="modal-header">
36
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
37
+ <h3 id="myModalLabel">Add IP to <p class="feature_name"></p></h3>
38
+ </div>
39
+ <div class="modal-body">
40
+ <p>IP address</p>
41
+ <input type="text" name="ipaddr" id="ipaddr">
42
+ <p>Please type in the ip address you would like to add.</p> </div>
43
+ <div class="modal-footer">
44
+ <button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
45
+ <button class="btn btn-primary">Save changes</button>
46
+ </div>
47
+ </div>
@@ -0,0 +1,26 @@
1
+ <div class="row">
2
+ <div class="span12">
3
+ <div class="page-header">
4
+ <h1><small>available features</small></h1>
5
+ </div>
6
+ <div class="row">
7
+ <div class="span8">
8
+ <% @features.each { |f| %>
9
+ <%= render :partial => "feature", :object => f%>
10
+ <% } %>
11
+ </div>
12
+ <div class="span4">
13
+ <h4>Add another features</h4>
14
+ <%= form_tag(create_path, :method => "post") do %>
15
+ <%= label_tag(:feature_name, "Feature name") %>
16
+ <%= text_field_tag(:feature_name) %>
17
+ <%= submit_tag("Create") %>
18
+ <p><br /><b>Attention</b><br />
19
+ Adding a feature only means that you defined a feature within the your redis database. You have to write the code yourself that enables the functionality.
20
+ </p>
21
+ <% end %>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ </div>
26
+ <%= render :partial => "modals"%>