rollout_admin 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +23 -0
- data/Rakefile +40 -0
- data/app/assets/images/rollout_admin/head_bg.jpg +0 -0
- data/app/assets/images/rollout_admin/icon_todos50x50.png +0 -0
- data/app/assets/images/rollout_admin/main_bg.jpg +0 -0
- data/app/assets/javascripts/rollout_admin/admin.js.erb +150 -0
- data/app/assets/javascripts/rollout_admin/application.js +19 -0
- data/app/assets/javascripts/rollout_admin/failures.js +2 -0
- data/app/assets/stylesheets/rollout_admin/admin.css +4 -0
- data/app/assets/stylesheets/rollout_admin/application.css +16 -0
- data/app/assets/stylesheets/rollout_admin/override.css.less +102 -0
- data/app/controllers/rollout_admin/admin_controller.rb +90 -0
- data/app/controllers/rollout_admin/application_controller.rb +4 -0
- data/app/helpers/rollout_admin/admin_helper.rb +4 -0
- data/app/helpers/rollout_admin/application_helper.rb +4 -0
- data/app/views/layouts/rollout_admin/application.html.erb +23 -0
- data/app/views/rollout_admin/admin/_feature.html.erb +71 -0
- data/app/views/rollout_admin/admin/_modals.html.erb +47 -0
- data/app/views/rollout_admin/admin/index.html.erb +26 -0
- data/config/routes.rb +12 -0
- data/lib/rollout_admin/engine.rb +5 -0
- data/lib/rollout_admin/version.rb +3 -0
- data/lib/rollout_admin.rb +4 -0
- data/lib/tasks/rollout_admin_tasks.rake +4 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config/application.rb +59 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +67 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/rollout.rb +2 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/log/development.log +20262 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/dummy/tmp/cache/assets/C20/C40/sprockets%2F261100664832a5fd690af826f0851667 +0 -0
- data/test/dummy/tmp/cache/assets/C55/400/sprockets%2F9922ee592c403b460458680f1da79017 +0 -0
- data/test/dummy/tmp/cache/assets/C6D/6B0/sprockets%2F429143f762968712de61fb6c8629669e +0 -0
- data/test/dummy/tmp/cache/assets/C70/A20/sprockets%2F109d1361d6d5143160a288424a1b9ff3 +0 -0
- data/test/dummy/tmp/cache/assets/C78/460/sprockets%2F7d9501489510b06f2c36b455b1538e1b +0 -0
- data/test/dummy/tmp/cache/assets/C7F/680/sprockets%2F8d04822934a82a4e26599123a06fe17c +0 -0
- data/test/dummy/tmp/cache/assets/C8D/500/sprockets%2F2197959d63ad398e3abb7116574d7073 +0 -0
- data/test/dummy/tmp/cache/assets/C8E/370/sprockets%2F5715a23b738069dc623ff5d778259c70 +0 -0
- data/test/dummy/tmp/cache/assets/C96/930/sprockets%2F528e0632943be0f0d7c329f775a99688 +0 -0
- data/test/dummy/tmp/cache/assets/C9C/D90/sprockets%2F4a2d944b82b6411b4e3030cea0036896 +0 -0
- data/test/dummy/tmp/cache/assets/CA6/720/sprockets%2Fe54f120734cb3306547c49e23cd5c542 +0 -0
- data/test/dummy/tmp/cache/assets/CAB/130/sprockets%2Ff337a04f80ff3450c4103668a9064ad9 +0 -0
- data/test/dummy/tmp/cache/assets/CAE/F10/sprockets%2F16ff235a3919c04c0f0478414e59ca74 +0 -0
- data/test/dummy/tmp/cache/assets/CB3/100/sprockets%2F7187f0a20c6384eb48c29c70d008e388 +0 -0
- data/test/dummy/tmp/cache/assets/CB4/E00/sprockets%2F461896fc5136e1a54ca861480fb25f28 +0 -0
- data/test/dummy/tmp/cache/assets/CB5/DD0/sprockets%2Fc67208c4492b96c4332748c2c3e271ff +0 -0
- data/test/dummy/tmp/cache/assets/CB7/590/sprockets%2F764ce84c5b819b86fe552304ac814362 +0 -0
- data/test/dummy/tmp/cache/assets/CB9/6A0/sprockets%2Fd930e22f79e95b9b0331664d51666da6 +0 -0
- data/test/dummy/tmp/cache/assets/CBE/700/sprockets%2F00d72dab59c7e0a107102b8334b1e108 +0 -0
- data/test/dummy/tmp/cache/assets/CC1/1C0/sprockets%2Feb1b6997ba729596eb417627dc077345 +0 -0
- data/test/dummy/tmp/cache/assets/CC1/DA0/sprockets%2F675f039576d2fdc56653c7bc63d12875 +0 -0
- data/test/dummy/tmp/cache/assets/CC7/0C0/sprockets%2F7860733be5a69b995b7c9634530cf8e4 +0 -0
- data/test/dummy/tmp/cache/assets/CD2/3D0/sprockets%2F6fa3247d6904347a087bcb60ae4302f2 +0 -0
- data/test/dummy/tmp/cache/assets/CD2/B80/sprockets%2Ff31b41a931d6f0183c5cb0895483d21b +0 -0
- data/test/dummy/tmp/cache/assets/CD3/980/sprockets%2F03077d801d9c005f9a10f463f646c0fa +0 -0
- data/test/dummy/tmp/cache/assets/CD5/650/sprockets%2F3a67e7b7c24cb9f219112470c8f0304c +0 -0
- data/test/dummy/tmp/cache/assets/CD5/C00/sprockets%2F72b822081ea29032c5b7e7586bb5a22e +0 -0
- data/test/dummy/tmp/cache/assets/CD8/730/sprockets%2F32aabf38943204760722ca7e0c7fb288 +0 -0
- data/test/dummy/tmp/cache/assets/CDC/030/sprockets%2F5c09dc685263e63bdee05d5111a68525 +0 -0
- data/test/dummy/tmp/cache/assets/CDF/F20/sprockets%2Fb377780bb495f9308adc8f8c8040300a +0 -0
- data/test/dummy/tmp/cache/assets/CDF/FA0/sprockets%2Ff3348795f69c512ef32fce01d7006f00 +0 -0
- data/test/dummy/tmp/cache/assets/CEB/E40/sprockets%2Fdc0c77867a41f6512d992f5d380e429b +0 -0
- data/test/dummy/tmp/cache/assets/CEE/E80/sprockets%2F5a88f9906573e2187c61b4ce801e96ab +0 -0
- data/test/dummy/tmp/cache/assets/CF4/6A0/sprockets%2Fe14b6b684fe680f38f1166df49586b44 +0 -0
- data/test/dummy/tmp/cache/assets/CF8/220/sprockets%2F58cc7867e2797caf2081e9c8051c549f +0 -0
- data/test/dummy/tmp/cache/assets/D00/E70/sprockets%2F11823b27c41eed2102a188a853cbd8d7 +0 -0
- data/test/dummy/tmp/cache/assets/D04/190/sprockets%2F462b137adb8c4e37522d854b6a2eb730 +0 -0
- data/test/dummy/tmp/cache/assets/D06/ED0/sprockets%2F708d4ef91c962e6cb60012b0e2c53f25 +0 -0
- data/test/dummy/tmp/cache/assets/D0D/CA0/sprockets%2Fbb60dc938786ab2a91b28706cb32a298 +0 -0
- data/test/dummy/tmp/cache/assets/D0E/380/sprockets%2F4b12d7f74747f23c6017a4a7e965ab3c +0 -0
- data/test/dummy/tmp/cache/assets/D0E/760/sprockets%2Ffe56acc145c40e203265082f584e7d7e +0 -0
- data/test/dummy/tmp/cache/assets/D0E/ED0/sprockets%2F9261ee78b6d57ac5fe6171cb17114b61 +0 -0
- data/test/dummy/tmp/cache/assets/D10/C50/sprockets%2F6075c7bc4cc3b2d04d54bbc934709697 +0 -0
- data/test/dummy/tmp/cache/assets/D13/6F0/sprockets%2Fb12b479efabdb654778797e40044fa31 +0 -0
- data/test/dummy/tmp/cache/assets/D16/B20/sprockets%2F3e87067a172937368f3ddfdabcd14445 +0 -0
- data/test/dummy/tmp/cache/assets/D1F/440/sprockets%2Fd3862d1d68f97615f146311eab7d7f9d +0 -0
- data/test/dummy/tmp/cache/assets/D24/D00/sprockets%2F76e4073a159f5418f55ee35ca93f97ec +0 -0
- data/test/dummy/tmp/cache/assets/D28/0F0/sprockets%2Ff220f24f7de79602db8669ca8a18997f +0 -0
- data/test/dummy/tmp/cache/assets/D2C/D90/sprockets%2F3ec24ca7b474681701e355aa229bca3b +0 -0
- data/test/dummy/tmp/cache/assets/D32/1F0/sprockets%2Fe1af7c212b842ba6186afa058252c9f2 +0 -0
- data/test/dummy/tmp/cache/assets/D33/B80/sprockets%2F1c3371fbdb4f59fd006cb903018d257c +0 -0
- data/test/dummy/tmp/cache/assets/D3A/300/sprockets%2Ffaa86f001fde572c3be445509308c3b9 +0 -0
- data/test/dummy/tmp/cache/assets/D3E/340/sprockets%2Fc62625e05a6ab78fa7108bc91219ff6c +0 -0
- data/test/dummy/tmp/cache/assets/D42/130/sprockets%2F0ce4f38cdd901f2fb060876f217492df +0 -0
- data/test/dummy/tmp/cache/assets/D43/A00/sprockets%2F0af849366f9470e63fc22ec28ecc9a00 +0 -0
- data/test/dummy/tmp/cache/assets/D44/E80/sprockets%2F66ae491aae5c0e18e599d45e22e552b6 +0 -0
- data/test/dummy/tmp/cache/assets/D45/270/sprockets%2Fa9573efccd81ee042694caf103663e85 +0 -0
- data/test/dummy/tmp/cache/assets/D48/3F0/sprockets%2Fc73b9787cfd00e9ef343185e511fc3e3 +0 -0
- data/test/dummy/tmp/cache/assets/D4F/160/sprockets%2Fa05fd4653009b8a45acb40f0ad2d412b +0 -0
- data/test/dummy/tmp/cache/assets/D4F/B50/sprockets%2F70467bdfb28b8966960e3d0c589e2add +0 -0
- data/test/dummy/tmp/cache/assets/D55/3C0/sprockets%2F5a0966ba1bff69a36fe2f39473f9b399 +0 -0
- data/test/dummy/tmp/cache/assets/D6B/250/sprockets%2Fedf4f5f302a722e3fd6f6509b3ed1810 +0 -0
- data/test/dummy/tmp/cache/assets/D6B/B30/sprockets%2Fb7cf44c5e1ea68800d9e3a9235ae5a60 +0 -0
- data/test/dummy/tmp/cache/assets/D6C/0C0/sprockets%2F1ac2e63a2b7fd76b515eb5d95b2856a5 +0 -0
- data/test/dummy/tmp/cache/assets/D6C/B30/sprockets%2F1d5ec72e04cd10fb8c90bbda87088817 +0 -0
- data/test/dummy/tmp/cache/assets/D6D/870/sprockets%2Fee06e0b902601c67d24c9fb7a3cbe696 +0 -0
- data/test/dummy/tmp/cache/assets/D71/C20/sprockets%2Fc7303b23dce837fa7b76f4125fc4e46e +0 -0
- data/test/dummy/tmp/cache/assets/D71/FF0/sprockets%2Fc57f0246fc0b8ee64dc164f34a52c6d9 +0 -0
- data/test/dummy/tmp/cache/assets/D72/7C0/sprockets%2Fef5c15a386af59791adc70444adc93d2 +0 -0
- data/test/dummy/tmp/cache/assets/D74/A80/sprockets%2F18ba650d0ac50053fe8cf97f9db98f31 +0 -0
- data/test/dummy/tmp/cache/assets/D7A/530/sprockets%2Fa45e2fe38b2980a24c715997edfbb3e7 +0 -0
- data/test/dummy/tmp/cache/assets/D83/AC0/sprockets%2F784c9ffae2dd6a84e91293a8b078cd83 +0 -0
- data/test/dummy/tmp/cache/assets/D90/660/sprockets%2F1cc7ddd5f049da1ab735a137e7424b3c +0 -0
- data/test/dummy/tmp/cache/assets/D97/3F0/sprockets%2F73b6a9e91e1c3dfbde7df506063f030a +0 -0
- data/test/dummy/tmp/cache/assets/D9A/F70/sprockets%2F9b3298a5ca38cbfb3fb9a062ee36b180 +0 -0
- data/test/dummy/tmp/cache/assets/DA1/B70/sprockets%2F6fd2228ab269ca977b97aab086c60fda +0 -0
- data/test/dummy/tmp/cache/assets/DA2/090/sprockets%2F472d4241ad9f4b6e791f5f8dfd31ce0a +0 -0
- data/test/dummy/tmp/cache/assets/DA2/BB0/sprockets%2F20dc7d35189b5b8b2de1d4d072e9ff7c +0 -0
- data/test/dummy/tmp/cache/assets/DA6/C70/sprockets%2F78df72bc3eb5d94abf5f652cb20708e7 +0 -0
- data/test/dummy/tmp/cache/assets/DA6/F90/sprockets%2Fe75e1ebb6089a1862bfdb14588f8d3ac +0 -0
- data/test/dummy/tmp/cache/assets/DAA/420/sprockets%2Ff4b2a98efc26e6289263cc0d3bd79c4e +0 -0
- data/test/dummy/tmp/cache/assets/DAC/F50/sprockets%2Faf5a558648cd7bc81e5b85df6fa125d7 +0 -0
- data/test/dummy/tmp/cache/assets/DB2/0A0/sprockets%2Faeac771697881809329b2ebeeced8bf6 +0 -0
- data/test/dummy/tmp/cache/assets/DBD/330/sprockets%2F51a9cf8afaaa9b816cb8a70ac7512a21 +0 -0
- data/test/dummy/tmp/cache/assets/DBF/B40/sprockets%2Fe00c05fe811a5201ecf81e1af8e6eca8 +0 -0
- data/test/dummy/tmp/cache/assets/DC1/AB0/sprockets%2F2e20ea6c418db23cb9c2cf1782a95cad +0 -0
- data/test/dummy/tmp/cache/assets/DCB/240/sprockets%2F23b7eecc9a7c0c38f7c5316efaa60e17 +0 -0
- data/test/dummy/tmp/cache/assets/DD2/1E0/sprockets%2F9ead9f72b0d11cb7a9f5637f77ab13be +0 -0
- data/test/dummy/tmp/cache/assets/DD9/300/sprockets%2Fa24d5e2b2b38e7faf91cf3738d6f48ec +0 -0
- data/test/dummy/tmp/cache/assets/DDB/AA0/sprockets%2Ff9b53bed9958305cff6ff8dc0f330bd2 +0 -0
- data/test/dummy/tmp/cache/assets/DDC/990/sprockets%2F4a1cef3fbac3d74d9a15d3997ed9918c +0 -0
- data/test/dummy/tmp/cache/assets/DDD/E70/sprockets%2F0b0e2c06badeabd4ea9b52178b44a00e +0 -0
- data/test/dummy/tmp/cache/assets/DEF/610/sprockets%2Fe23e6b6dadeb8f01ab452bab578a532e +0 -0
- data/test/dummy/tmp/cache/assets/DF0/D30/sprockets%2F88ab3631daac054acf95528ce1acc4cd +0 -0
- data/test/dummy/tmp/cache/assets/DF4/4C0/sprockets%2Fa722b8a28bce4d3346bd0be5f46bdae7 +0 -0
- data/test/dummy/tmp/cache/assets/DFB/360/sprockets%2Fbe14b4380ce7db7ec289dbe3d4cfa092 +0 -0
- data/test/dummy/tmp/cache/assets/DFB/570/sprockets%2F15ee84ca369789a1c4adf2cc0a2ffea4 +0 -0
- data/test/dummy/tmp/cache/assets/DFC/E40/sprockets%2F8fde3a1113aeecd77fb2f27c0e276be8 +0 -0
- data/test/dummy/tmp/cache/assets/E0B/DF0/sprockets%2Fd1a8c47b74aa53edbb5288992ddedd9f +0 -0
- data/test/dummy/tmp/cache/assets/E33/010/sprockets%2Ffd7db48da51f060c6bdd80dfbd992d8b +0 -0
- data/test/dummy/tmp/cache/assets/E33/050/sprockets%2Fe5ce222fae640d1bf9f3fc96eaa87fb4 +0 -0
- data/test/dummy/tmp/cache/assets/E3D/4A0/sprockets%2F6954eff7c65c4d1fc8d8571aeecaafd3 +0 -0
- data/test/dummy/tmp/cache/assets/EBF/FA0/sprockets%2Fafa901c76afcff908decda3eea27b9be +0 -0
- data/test/functional/rollout_admin/admin_controller_test.rb +11 -0
- data/test/integration/navigation_test.rb +10 -0
- data/test/rollout_admin_test.rb +7 -0
- data/test/test_helper.rb +15 -0
- data/test/unit/helpers/rollout_admin/admin_helper_test.rb +6 -0
- 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
|
Binary file
|
Binary file
|
Binary file
|
@@ -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,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,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"%>
|