rollout_admin 0.0.1

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