├── .gitignore ├── .rspec ├── .travis.yml ├── Gemfile ├── Gemfile.lock ├── Guardfile ├── README.md ├── Rakefile ├── app ├── assets │ ├── images │ │ └── .keep │ ├── javascripts │ │ ├── application.js │ │ └── inventories.js │ └── stylesheets │ │ ├── application.css.scss │ │ ├── pace.css │ │ └── user.css ├── controllers │ ├── administration_controller.rb │ ├── api │ │ ├── api.rb │ │ ├── v1 │ │ │ └── api.rb │ │ └── v2 │ │ │ └── api.rb │ ├── application_controller.rb │ ├── concerns │ │ ├── .keep │ │ └── inventory_action.rb │ ├── groups_controller.rb │ ├── hosts_controller.rb │ ├── identities_controller.rb │ ├── inventories_controller.rb │ ├── sessions_controller.rb │ ├── static_pages_controller.rb │ └── user_controller.rb ├── helpers │ └── application_helper.rb ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── ability.rb │ ├── concerns │ │ ├── .keep │ │ ├── json_validator.rb │ │ ├── secure_key.rb │ │ └── variabable.rb │ ├── group.rb │ ├── group_host.rb │ ├── host.rb │ ├── identity.rb │ ├── inventory.rb │ └── user.rb └── views │ ├── administration │ └── user.html.erb │ ├── groups │ ├── _form.html.erb │ ├── edit.html.erb │ └── new.html.erb │ ├── hosts │ ├── _form.html.erb │ ├── edit.html.erb │ └── new.html.erb │ ├── identities │ └── new.html.erb │ ├── inventories │ ├── _form.html.erb │ ├── _groups.html.erb │ ├── _hosts.html.erb │ ├── edit.html.erb │ ├── index.html.erb │ ├── new.html.erb │ └── show.html.erb │ ├── layouts │ ├── _flash.html.erb │ ├── _footer.html.erb │ ├── _navigation.html.erb │ ├── _navigation_menu_left.html.erb │ ├── _navigation_menu_right.html.erb │ └── application.html.erb │ ├── sessions │ └── new.html.erb │ ├── shared │ ├── _form_errors.html.erb │ └── _form_submit_cancel.html.erb │ ├── static_pages │ └── help.html.erb │ └── user │ ├── _change_password.html.erb │ └── show.html.erb ├── bin ├── bundle ├── rails ├── rake └── rspec ├── config.ru ├── config ├── application.rb ├── boot.rb ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── backtrace_silencers.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── omniauth.rb │ ├── secret_token.rb │ ├── session_store.rb │ └── wrap_parameters.rb ├── locales │ └── en.yml └── routes.rb ├── db ├── migrate │ ├── 20130923160656_create_users.rb │ ├── 20130923161052_create_identities.rb │ ├── 20130924082325_create_groups.rb │ ├── 20130924082459_create_hosts.rb │ ├── 20130924082519_create_inventories.rb │ └── 20130925144907_create_group_hosts.rb ├── schema.rb └── seeds.rb ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── log └── .keep ├── public ├── 404.html ├── 422.html ├── 500.html ├── download │ ├── inventory-manager │ └── inventory-manager.ini ├── favicon.ico └── robots.txt ├── spec ├── factories │ ├── group.rb │ ├── host.rb │ ├── inventory.rb │ ├── user.rb │ └── utils.rb ├── features │ ├── inventory_spec.rb │ └── session_spec.rb ├── helpers │ └── application_helper_spec.rb ├── models │ ├── group_spec.rb │ ├── host_spec.rb │ └── inventory_spec.rb ├── omniauth_helper.rb ├── requests │ └── api_v1_spec.rb └── spec_helper.rb └── vendor └── assets ├── javascripts ├── .keep ├── js-yaml.js └── pace.min.js └── stylesheets └── .keep /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/*.log 16 | /tmp 17 | coverage/ 18 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.0.0 4 | notifications: 5 | email: false -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | ruby '2.0.0' 3 | 4 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 5 | gem 'rails', '4.0.0' 6 | 7 | # Use sqlite3 as the database for Active Record 8 | gem 'sqlite3', group: [:test, :development] 9 | gem 'pg', group: :production 10 | 11 | # Use SCSS for stylesheets 12 | gem 'sass-rails', '~> 4.0.0' 13 | 14 | # Use Uglifier as compressor for JavaScript assets 15 | gem 'uglifier', '>= 1.3.0' 16 | 17 | # Use CoffeeScript for .js.coffee assets and views 18 | gem 'coffee-rails', '~> 4.0.0' 19 | 20 | # See https://github.com/sstephenson/execjs#readme for more supported runtimes 21 | # gem 'therubyracer', platforms: :ruby 22 | 23 | # Use jquery as the JavaScript library 24 | gem 'jquery-rails' 25 | gem 'jquery-turbolinks' 26 | 27 | # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks 28 | gem 'turbolinks' 29 | 30 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 31 | gem 'jbuilder', '~> 1.2' 32 | 33 | # Grape 34 | gem 'grape' 35 | 36 | # OmniAuth 37 | gem 'omniauth-identity' 38 | 39 | # Bootstrap 40 | gem 'anjlab-bootstrap-rails', :require => 'bootstrap-rails', 41 | :github => 'anjlab/bootstrap-rails' 42 | 43 | # cancan 44 | gem 'cancan' 45 | 46 | group :doc do 47 | # bundle exec rake doc:rails generates the API under doc/api. 48 | gem 'sdoc', require: false 49 | end 50 | 51 | # Use ActiveModel has_secure_password 52 | # gem 'bcrypt-ruby', '~> 3.0.0' 53 | 54 | # Use unicorn as the app server 55 | # gem 'unicorn' 56 | 57 | # Use Capistrano for deployment 58 | # gem 'capistrano', group: :development 59 | 60 | # Use debugger 61 | # gem 'debugger', group: [:development, :test] 62 | 63 | group :development, :test do 64 | gem 'rspec-rails', '~> 3.0.0.beta' 65 | gem 'capybara' 66 | gem 'factory_girl_rails' 67 | 68 | gem 'rake' 69 | gem 'coveralls', require: false 70 | end 71 | 72 | group :development do 73 | gem 'guard' 74 | gem 'guard-rspec' 75 | gem 'terminal-notifier-guard', require: false 76 | gem 'launchy' 77 | end 78 | 79 | 80 | # heroku 81 | gem 'rails_12factor', group: :production 82 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/anjlab/bootstrap-rails.git 3 | revision: 3070835a95975c0d2417094825633bcb763f0182 4 | specs: 5 | anjlab-bootstrap-rails (3.0.0.3) 6 | railties (>= 3.0) 7 | sass (>= 3.2) 8 | 9 | GEM 10 | remote: https://rubygems.org/ 11 | specs: 12 | actionmailer (4.0.0) 13 | actionpack (= 4.0.0) 14 | mail (~> 2.5.3) 15 | actionpack (4.0.0) 16 | activesupport (= 4.0.0) 17 | builder (~> 3.1.0) 18 | erubis (~> 2.7.0) 19 | rack (~> 1.5.2) 20 | rack-test (~> 0.6.2) 21 | activemodel (4.0.0) 22 | activesupport (= 4.0.0) 23 | builder (~> 3.1.0) 24 | activerecord (4.0.0) 25 | activemodel (= 4.0.0) 26 | activerecord-deprecated_finders (~> 1.0.2) 27 | activesupport (= 4.0.0) 28 | arel (~> 4.0.0) 29 | activerecord-deprecated_finders (1.0.3) 30 | activesupport (4.0.0) 31 | i18n (~> 0.6, >= 0.6.4) 32 | minitest (~> 4.2) 33 | multi_json (~> 1.3) 34 | thread_safe (~> 0.1) 35 | tzinfo (~> 0.3.37) 36 | addressable (2.3.5) 37 | arel (4.0.0) 38 | atomic (1.1.14) 39 | axiom-types (0.0.5) 40 | descendants_tracker (~> 0.0.1) 41 | ice_nine (~> 0.9) 42 | backports (3.3.5) 43 | bcrypt-ruby (3.0.1) 44 | builder (3.1.4) 45 | cancan (1.6.10) 46 | capybara (2.2.0) 47 | mime-types (>= 1.16) 48 | nokogiri (>= 1.3.3) 49 | rack (>= 1.0.0) 50 | rack-test (>= 0.5.4) 51 | xpath (~> 2.0) 52 | celluloid (0.15.2) 53 | timers (~> 1.1.0) 54 | coderay (1.1.0) 55 | coercible (0.2.0) 56 | backports (~> 3.0, >= 3.1.0) 57 | descendants_tracker (~> 0.0.1) 58 | coffee-rails (4.0.0) 59 | coffee-script (>= 2.2.0) 60 | railties (>= 4.0.0.beta, < 5.0) 61 | coffee-script (2.2.0) 62 | coffee-script-source 63 | execjs 64 | coffee-script-source (1.6.3) 65 | coveralls (0.7.0) 66 | multi_json (~> 1.3) 67 | rest-client 68 | simplecov (>= 0.7) 69 | term-ansicolor 70 | thor 71 | descendants_tracker (0.0.3) 72 | diff-lcs (1.2.5) 73 | docile (1.1.1) 74 | equalizer (0.0.8) 75 | erubis (2.7.0) 76 | execjs (2.0.1) 77 | factory_girl (4.3.0) 78 | activesupport (>= 3.0.0) 79 | factory_girl_rails (4.3.0) 80 | factory_girl (~> 4.3.0) 81 | railties (>= 3.0.0) 82 | ffi (1.9.3) 83 | formatador (0.2.4) 84 | grape (0.6.1) 85 | activesupport 86 | builder 87 | hashie (>= 1.2.0) 88 | multi_json (>= 1.3.2) 89 | multi_xml (>= 0.5.2) 90 | rack (>= 1.3.0) 91 | rack-accept 92 | rack-mount 93 | virtus (>= 1.0.0) 94 | guard (2.2.5) 95 | formatador (>= 0.2.4) 96 | listen (~> 2.1) 97 | lumberjack (~> 1.0) 98 | pry (>= 0.9.12) 99 | thor (>= 0.18.1) 100 | guard-rspec (1.2.1) 101 | guard (>= 1.1) 102 | hashie (2.0.5) 103 | hike (1.2.3) 104 | i18n (0.6.5) 105 | ice_nine (0.10.0) 106 | jbuilder (1.5.1) 107 | activesupport (>= 3.0.0) 108 | multi_json (>= 1.2.0) 109 | jquery-rails (3.0.4) 110 | railties (>= 3.0, < 5.0) 111 | thor (>= 0.14, < 2.0) 112 | jquery-turbolinks (2.0.1) 113 | railties (>= 3.1.0) 114 | turbolinks 115 | json (1.8.0) 116 | launchy (2.4.2) 117 | addressable (~> 2.3) 118 | listen (2.4.0) 119 | celluloid (>= 0.15.2) 120 | rb-fsevent (>= 0.9.3) 121 | rb-inotify (>= 0.9) 122 | lumberjack (1.0.4) 123 | mail (2.5.4) 124 | mime-types (~> 1.16) 125 | treetop (~> 1.4.8) 126 | method_source (0.8.2) 127 | mime-types (1.25) 128 | mini_portile (0.5.2) 129 | minitest (4.7.5) 130 | multi_json (1.8.0) 131 | multi_xml (0.5.5) 132 | nokogiri (1.6.0) 133 | mini_portile (~> 0.5.0) 134 | omniauth (1.1.4) 135 | hashie (>= 1.2, < 3) 136 | rack 137 | omniauth-identity (1.1.0) 138 | bcrypt-ruby (~> 3.0) 139 | omniauth (~> 1.0) 140 | pg (0.15.1) 141 | polyglot (0.3.3) 142 | pry (0.9.12.4) 143 | coderay (~> 1.0) 144 | method_source (~> 0.8) 145 | slop (~> 3.4) 146 | rack (1.5.2) 147 | rack-accept (0.4.5) 148 | rack (>= 0.4) 149 | rack-mount (0.8.3) 150 | rack (>= 1.0.0) 151 | rack-test (0.6.2) 152 | rack (>= 1.0) 153 | rails (4.0.0) 154 | actionmailer (= 4.0.0) 155 | actionpack (= 4.0.0) 156 | activerecord (= 4.0.0) 157 | activesupport (= 4.0.0) 158 | bundler (>= 1.3.0, < 2.0) 159 | railties (= 4.0.0) 160 | sprockets-rails (~> 2.0.0) 161 | rails_12factor (0.0.2) 162 | rails_serve_static_assets 163 | rails_stdout_logging 164 | rails_serve_static_assets (0.0.1) 165 | rails_stdout_logging (0.0.2) 166 | railties (4.0.0) 167 | actionpack (= 4.0.0) 168 | activesupport (= 4.0.0) 169 | rake (>= 0.8.7) 170 | thor (>= 0.18.1, < 2.0) 171 | rake (10.1.0) 172 | rb-fsevent (0.9.3) 173 | rb-inotify (0.9.3) 174 | ffi (>= 0.5.0) 175 | rdoc (3.12.2) 176 | json (~> 1.4) 177 | rest-client (1.6.7) 178 | mime-types (>= 1.16) 179 | rspec-collection_matchers (0.0.2) 180 | rspec-expectations (>= 2.99.0.beta1) 181 | rspec-core (3.0.0.beta1) 182 | rspec-expectations (3.0.0.beta1) 183 | diff-lcs (>= 1.1.3, < 2.0) 184 | rspec-support (= 3.0.0.beta1) 185 | rspec-mocks (3.0.0.beta1) 186 | rspec-rails (3.0.0.beta1) 187 | actionpack (>= 3.0) 188 | activemodel (>= 3.0) 189 | activesupport (>= 3.0) 190 | railties (>= 3.0) 191 | rspec-collection_matchers 192 | rspec-core (= 3.0.0.beta1) 193 | rspec-expectations (= 3.0.0.beta1) 194 | rspec-mocks (= 3.0.0.beta1) 195 | rspec-support (3.0.0.beta1) 196 | sass (3.2.10) 197 | sass-rails (4.0.0) 198 | railties (>= 4.0.0.beta, < 5.0) 199 | sass (>= 3.1.10) 200 | sprockets-rails (~> 2.0.0) 201 | sdoc (0.3.20) 202 | json (>= 1.1.3) 203 | rdoc (~> 3.10) 204 | simplecov (0.8.2) 205 | docile (~> 1.1.0) 206 | multi_json 207 | simplecov-html (~> 0.8.0) 208 | simplecov-html (0.8.0) 209 | slop (3.4.7) 210 | sprockets (2.10.0) 211 | hike (~> 1.2) 212 | multi_json (~> 1.0) 213 | rack (~> 1.0) 214 | tilt (~> 1.1, != 1.3.0) 215 | sprockets-rails (2.0.0) 216 | actionpack (>= 3.0) 217 | activesupport (>= 3.0) 218 | sprockets (~> 2.8) 219 | sqlite3 (1.3.8) 220 | term-ansicolor (1.2.2) 221 | tins (~> 0.8) 222 | terminal-notifier-guard (1.5.3) 223 | thor (0.18.1) 224 | thread_safe (0.1.3) 225 | atomic 226 | tilt (1.4.1) 227 | timers (1.1.0) 228 | tins (0.13.1) 229 | treetop (1.4.15) 230 | polyglot 231 | polyglot (>= 0.3.1) 232 | turbolinks (1.3.0) 233 | coffee-rails 234 | tzinfo (0.3.37) 235 | uglifier (2.2.1) 236 | execjs (>= 0.3.0) 237 | multi_json (~> 1.0, >= 1.0.2) 238 | virtus (1.0.0) 239 | axiom-types (~> 0.0.5) 240 | coercible (~> 0.2) 241 | descendants_tracker (~> 0.0.1) 242 | equalizer (~> 0.0.7) 243 | xpath (2.0.0) 244 | nokogiri (~> 1.3) 245 | 246 | PLATFORMS 247 | ruby 248 | 249 | DEPENDENCIES 250 | anjlab-bootstrap-rails! 251 | cancan 252 | capybara 253 | coffee-rails (~> 4.0.0) 254 | coveralls 255 | factory_girl_rails 256 | grape 257 | guard 258 | guard-rspec 259 | jbuilder (~> 1.2) 260 | jquery-rails 261 | jquery-turbolinks 262 | launchy 263 | omniauth-identity 264 | pg 265 | rails (= 4.0.0) 266 | rails_12factor 267 | rake 268 | rspec-rails (~> 3.0.0.beta) 269 | sass-rails (~> 4.0.0) 270 | sdoc 271 | sqlite3 272 | terminal-notifier-guard 273 | turbolinks 274 | uglifier (>= 1.3.0) 275 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | guard 'rspec', :version => 2 do 5 | watch(%r{^spec/.+_spec\.rb$}) 6 | watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } 7 | watch('spec/spec_helper.rb') { "spec" } 8 | 9 | # Rails example 10 | watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } 11 | watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } 12 | watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] } 13 | watch(%r{^spec/support/(.+)\.rb$}) { "spec" } 14 | watch('config/routes.rb') { "spec/routing" } 15 | watch('app/controllers/application_controller.rb') { "spec/controllers" } 16 | 17 | # Capybara request specs 18 | watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" } 19 | 20 | # Turnip features and steps 21 | watch(%r{^spec/acceptance/(.+)\.feature$}) 22 | watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' } 23 | end 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ansible Inventory Manager [![Build Status](https://travis-ci.org/pgolm/ansible-inventory-manager.png)](https://travis-ci.org/pgolm/ansible-inventory-manager) [![Coverage Status](https://coveralls.io/repos/pgolm/ansible-inventory-manager/badge.png?branch=master)](https://coveralls.io/r/pgolm/ansible-inventory-manager?branch=master) 2 | 3 | A Web UI to manage ansible inventories. 4 | 5 | [DEMO](http://ansible-inventory-manager-demo.herokuapp.com/) 6 | 7 | ## Deploy on Heroku 8 | 9 | ``` 10 | heroku create 11 | heroku addons:add heroku-postgresql:dev 12 | git push heroku master 13 | heroku run rake db:setup 14 | ``` 15 | 16 | Login with: 17 | 18 | ``` 19 | User: admin@example.com 20 | Password: admin 21 | ``` 22 | 23 | ## Configuration 24 | 25 | ### Allow user registration 26 | Set ```config.registration = true``` in *config/application.rb*. 27 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | AnsibleWebInventory::Application.load_tasks 7 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pgolm/ansible-inventory-manager/670cc22617e919a87b9dd882e34a5f3978fddc39/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 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 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require jquery.turbolinks 16 | //= require turbolinks 17 | //= require pace.min 18 | //= require twitter/bootstrap/alert 19 | //= require twitter/bootstrap/button 20 | //= require twitter/bootstrap/collapse 21 | //= require twitter/bootstrap/dropdown 22 | //= require_tree . 23 | //= require js-yaml -------------------------------------------------------------------------------- /app/assets/javascripts/inventories.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | // beautify json 3 | var var_area = $('#var_area'); 4 | 5 | if(var_area.length > 0) { 6 | var_area.val( 7 | JSON.stringify(JSON.parse(var_area.val()), null, 4) 8 | ); 9 | } 10 | 11 | $('#variable-format-switch input').change(function() { 12 | if(this.id == 'yaml-format') { 13 | var json_data = JSON.parse(var_area.val()) 14 | if(jQuery.isEmptyObject(json_data)) { 15 | var_area.val("") 16 | } else { 17 | var_area.val( 18 | jsyaml.safeDump(json_data) 19 | ); 20 | } 21 | } else { 22 | var_area.val( 23 | JSON.stringify(jsyaml.safeLoad(var_area.val()) || {}, null, 4) 24 | ); 25 | } 26 | }) 27 | 28 | // Inventory Table 29 | $('#all-inventory-cb').click(function() { 30 | if($(this).hasClass('active')) { 31 | $('#inventory-table tbody > .warning').hide() 32 | } else { 33 | $('#inventory-table tbody > .warning').show() 34 | } 35 | }); 36 | 37 | // Init value 38 | $('#yaml-format').click() 39 | $('#all-inventory-cb').click().click(); 40 | }) -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | *= require_tree . 13 | */ 14 | 15 | @import "twitter/bootstrap"; 16 | 17 | html, body { 18 | height: 100%; 19 | } 20 | 21 | #wrap { 22 | min-height: 100%; 23 | height: auto !important; 24 | height: 100%; 25 | /* Negative indent footer by its height */ 26 | margin: 0 auto -60px; 27 | /* Pad bottom by footer height */ 28 | padding: 0 0 60px; 29 | } 30 | 31 | #footer { 32 | height: 60px; 33 | background-color: #f8f8f8; 34 | border: 1px solid #e7e7e7; 35 | } 36 | 37 | #wrap > .container { 38 | padding: 60px 15px 30px; 39 | } 40 | .container > p { 41 | margin-top: 20px; 42 | } 43 | 44 | #footer > .container { 45 | padding-left: 15px; 46 | padding-right: 15px; 47 | } 48 | 49 | #avatar-btn > a { 50 | padding-top: 11px; 51 | padding-bottom: 11px; 52 | padding-left: 1em; 53 | } 54 | -------------------------------------------------------------------------------- /app/assets/stylesheets/pace.css: -------------------------------------------------------------------------------- 1 | .pace .pace-progress { 2 | background: #c7c7c7; 3 | position: fixed; 4 | z-index: 2000; 5 | top: 49px; 6 | left: 0; 7 | height: 2px; 8 | 9 | -webkit-transition: width 1s; 10 | -moz-transition: width 1s; 11 | -o-transition: width 1s; 12 | transition: width 1s; 13 | } 14 | 15 | .pace-inactive { 16 | display: none; 17 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/user.css: -------------------------------------------------------------------------------- 1 | #api_key_btn { 2 | margin-left: 2em; 3 | } 4 | 5 | #change_password { 6 | padding-top: 2em; 7 | } -------------------------------------------------------------------------------- /app/controllers/administration_controller.rb: -------------------------------------------------------------------------------- 1 | class AdministrationController < ApplicationController 2 | def user 3 | authorize! :manage, User 4 | 5 | @users = User.all 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/api/api.rb: -------------------------------------------------------------------------------- 1 | Dir["#{Rails.root}/app/api/*.rb"].each {|file| require file} 2 | 3 | module API 4 | class API < Grape::API 5 | 6 | helpers do 7 | def authenticated? 8 | return false if params[:token].nil? 9 | User.exists?(api_key: params[:token]) 10 | end 11 | end 12 | 13 | mount V1::API 14 | mount V2::API 15 | end 16 | end -------------------------------------------------------------------------------- /app/controllers/api/v1/api.rb: -------------------------------------------------------------------------------- 1 | module API 2 | module V1 3 | module Entities 4 | class Inventory 5 | def initialize(inventory) 6 | @all = inventory.hosts.map(&:alias) 7 | 8 | inventory.groups.find_each do |group| 9 | self.instance_variable_set("@#{group.name}".to_sym, { 10 | hosts: group.hosts.map(&:alias), 11 | vars: ActiveSupport::JSON.decode(group.variables) 12 | }) 13 | end 14 | end 15 | end 16 | end 17 | 18 | class API < Grape::API 19 | format :json 20 | version 'v1', using: :path 21 | 22 | before do 23 | error!('Unauthorized', 401) unless authenticated? 24 | end 25 | 26 | params do 27 | requires :token, type: String 28 | end 29 | resource :inventory do 30 | 31 | desc "Inventory" 32 | params do 33 | requires :id, type: String 34 | end 35 | get ':id' do 36 | inventory = Inventory.find_by(key: params[:id]) 37 | error!('Not Found', 404) if inventory.nil? 38 | Entities::Inventory.new(inventory) 39 | end 40 | 41 | desc "Hostvariables" 42 | params do 43 | requires :id, type: String 44 | requires :host, type: String 45 | end 46 | get ':id/:host' do 47 | inventory = Inventory.find_by(key: params[:id]) 48 | error!('Not Found', 404) if inventory.nil? 49 | 50 | host = inventory.hosts.find_by(alias: params[:host]) 51 | error!('Not Found', 404) if host.nil? 52 | 53 | ActiveSupport::JSON.decode(host.variables) 54 | end 55 | end 56 | end 57 | end 58 | end -------------------------------------------------------------------------------- /app/controllers/api/v2/api.rb: -------------------------------------------------------------------------------- 1 | module API 2 | module V2 3 | module Entities 4 | class Inventory < V1::Entities::Inventory 5 | def initialize(inventory) 6 | super(inventory) 7 | 8 | @_meta = { hostvars: {} } 9 | inventory.hosts.find_each do |host| 10 | @_meta[:hostvars][host.alias] = ActiveSupport::JSON.decode(host.variables) 11 | end 12 | end 13 | end 14 | end 15 | 16 | class API < Grape::API 17 | format :json 18 | version 'v2', using: :path 19 | 20 | before do 21 | error!('Unauthorized', 401) unless authenticated? 22 | end 23 | 24 | params do 25 | requires :token, type: String 26 | end 27 | resource :inventory do 28 | desc "Inventory" 29 | get ':id' do 30 | inventory = Inventory.find_by(key: params[:id]) 31 | error!('Not Found', 404) if inventory.nil? 32 | 33 | Entities::Inventory.new(inventory) 34 | end 35 | end 36 | end 37 | end 38 | end -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :exception 5 | 6 | helper_method :current_user 7 | 8 | rescue_from CanCan::AccessDenied do |exception| 9 | redirect_to signin_path, alert: exception.message 10 | end 11 | 12 | private 13 | 14 | def current_user 15 | @current_user ||= User.find(session[:user_id]) if session[:user_id] 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pgolm/ansible-inventory-manager/670cc22617e919a87b9dd882e34a5f3978fddc39/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/concerns/inventory_action.rb: -------------------------------------------------------------------------------- 1 | module InventoryAction 2 | extend ActiveSupport::Concern 3 | 4 | protected 5 | 6 | def find_inventory 7 | @inventory = Inventory.find(params[:inventory_id]) 8 | end 9 | end -------------------------------------------------------------------------------- /app/controllers/groups_controller.rb: -------------------------------------------------------------------------------- 1 | class GroupsController < ApplicationController 2 | # cancan workaround 3 | before_filter only: :create do 4 | @group = Group.new(group_params) 5 | end 6 | 7 | load_and_authorize_resource 8 | 9 | include InventoryAction 10 | before_action :find_inventory, only: [:new, :create, :update, :destroy] 11 | 12 | def new 13 | @group = Group.new 14 | @group.inventory = @inventory 15 | end 16 | 17 | def create 18 | # @group = Group.new(group_params) 19 | if @group.save 20 | flash[:notice] = "Group was created" 21 | redirect_to @inventory 22 | else 23 | render :new 24 | end 25 | end 26 | 27 | def edit 28 | @group = Group.find(params[:id]) 29 | end 30 | 31 | def update 32 | @group = Group.find(params[:id]) 33 | 34 | if @group.update(group_params) 35 | flash[:notice] = "Group was updated." 36 | 37 | redirect_to @group.inventory 38 | else 39 | render :edit 40 | end 41 | end 42 | 43 | def destroy 44 | begin 45 | Group.find(params[:id]).destroy 46 | flash[:notice] = 'Group was deleted.' 47 | rescue 48 | flash[:alert] = "Group couldn't be deleted." 49 | end 50 | 51 | redirect_to @inventory 52 | end 53 | 54 | private 55 | 56 | # def find_inventory 57 | # @inventory = Inventory.find(params[:inventory_id]) 58 | # end 59 | 60 | def group_params 61 | params.require(:group).permit(:name, 62 | :description, 63 | :variables, 64 | :inventory_id, 65 | :hosts => []) 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /app/controllers/hosts_controller.rb: -------------------------------------------------------------------------------- 1 | class HostsController < ApplicationController 2 | # cancan workaround 3 | before_filter only: :create do 4 | @host = Host.new(host_params) 5 | end 6 | 7 | load_and_authorize_resource 8 | 9 | include InventoryAction 10 | before_action :find_inventory 11 | 12 | def new 13 | @host = Host.new 14 | @host.inventory = Inventory.find(params[:inventory_id]) 15 | end 16 | 17 | def create 18 | # @host = Host.new(host_params) 19 | if @host.save 20 | flash[:notice] = "Host was created." 21 | redirect_to inventory_path(params[:inventory_id]) 22 | else 23 | render :new 24 | end 25 | end 26 | 27 | def edit 28 | @host = Host.find(params[:id]) 29 | end 30 | 31 | def update 32 | @host = Host.find(params[:id]) 33 | 34 | if @host.update(host_params) 35 | flash[:notice] = "Host was updated." 36 | 37 | redirect_to @host.inventory 38 | else 39 | @inventory = Inventory.find(params[:inventory_id]) 40 | render :edit 41 | end 42 | end 43 | 44 | def destroy 45 | begin 46 | Host.find(params[:id]).destroy 47 | flash[:notice] = 'Host was deleted.' 48 | rescue 49 | flash[:alert] = "Host couldn't be deleted." 50 | end 51 | 52 | redirect_to inventory_path(params[:inventory_id]) 53 | end 54 | 55 | private 56 | 57 | def host_params 58 | params.require(:host).permit(:alias, 59 | :host, 60 | :description, 61 | :variables, 62 | :inventory_id, 63 | :groups => [] 64 | ) 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /app/controllers/identities_controller.rb: -------------------------------------------------------------------------------- 1 | class IdentitiesController < ApplicationController 2 | load_and_authorize_resource 3 | 4 | def new 5 | @identity = env['omniauth.identity'] 6 | end 7 | 8 | def update 9 | @identity = Identity.find(params[:id]) 10 | 11 | if @identity.update(identity_params) 12 | flash[:notice] = 'Password changed.' 13 | end 14 | 15 | redirect_to root_path 16 | end 17 | 18 | private 19 | 20 | def identity_params 21 | params.require(:identity).permit(:password, :password_confirmation) 22 | end 23 | 24 | end 25 | -------------------------------------------------------------------------------- /app/controllers/inventories_controller.rb: -------------------------------------------------------------------------------- 1 | class InventoriesController < ApplicationController 2 | # cancan workaround 3 | before_filter only: :create do 4 | @inventory = current_user.inventories.new(inventory_params) 5 | end 6 | 7 | load_and_authorize_resource 8 | 9 | def new 10 | @inventory = current_user.inventories.new 11 | end 12 | 13 | def create 14 | # @inventory = Inventory.new(inventory_params) 15 | if @inventory.save 16 | flash[:notice] = "Inventory was created." 17 | redirect_to @inventory 18 | else 19 | render :new 20 | end 21 | end 22 | 23 | def edit 24 | @inventory = Inventory.find(params[:id]) 25 | end 26 | 27 | def update 28 | @inventory = Inventory.find(params[:id]) 29 | if @inventory.update(inventory_params) 30 | flash[:notice] = "Inventory was updated." 31 | 32 | redirect_to @inventory 33 | else 34 | render :edit 35 | end 36 | end 37 | 38 | def show 39 | @inventory = Inventory.find(params[:id]) 40 | 41 | respond_to do |format| 42 | format.html 43 | format.text { render :text => @inventory.as_ansible_ini, :content_type => 'text/plain' } 44 | end 45 | end 46 | 47 | def index 48 | @inventories = Inventory.accessible_by(current_ability) 49 | end 50 | 51 | def destroy 52 | begin 53 | Inventory.find(params[:id]).destroy 54 | flash[:notice] = "Inventory was deleted." 55 | rescue 56 | flash[:alert] = "Inventory couldn't be deleted." 57 | end 58 | 59 | redirect_to inventories_path 60 | end 61 | 62 | private 63 | 64 | def inventory_params 65 | params.require(:inventory).permit(:name, :description) 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /app/controllers/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class SessionsController < ApplicationController 2 | def create 3 | auth = request.env["omniauth.auth"] 4 | user = User.find_by_provider_and_uid(auth["provider"], auth["uid"]) || User.create_with_omniauth(auth) 5 | session[:user_id] = user.id 6 | redirect_to root_url, :notice => "Signed in!" 7 | end 8 | 9 | def destroy 10 | session[:user_id] = nil 11 | redirect_to root_url, :notice => "Signed out!" 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/controllers/static_pages_controller.rb: -------------------------------------------------------------------------------- 1 | class StaticPagesController < ApplicationController 2 | end 3 | -------------------------------------------------------------------------------- /app/controllers/user_controller.rb: -------------------------------------------------------------------------------- 1 | class UserController < ApplicationController 2 | load_and_authorize_resource 3 | 4 | def show 5 | @user = User.find(params[:id]) 6 | end 7 | 8 | def update 9 | @user = User.find(params[:id]) 10 | 11 | if params[:user][:api_key] 12 | params[:user][:api_key] = User::next_secure_key 13 | flash[:notice] = 'New API Key was generated.' 14 | end 15 | 16 | if @user.update(user_params) 17 | redirect_to @user 18 | end 19 | end 20 | 21 | def destroy 22 | begin 23 | User.find(params[:id]).destroy 24 | flash[:notice] = "User was deleted." 25 | rescue 26 | flash[:alert] = "User couldn't be deleted." 27 | end 28 | 29 | redirect_to root_path 30 | end 31 | 32 | private 33 | 34 | def user_params 35 | params.require(:user).permit(:api_key) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | def avatar_url(user, size) 3 | gravatar_id = Digest::MD5.hexdigest(user.email.downcase) 4 | "http://gravatar.com/avatar/#{gravatar_id}.png?s=" + size.to_s + "&d=mm" 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pgolm/ansible-inventory-manager/670cc22617e919a87b9dd882e34a5f3978fddc39/app/mailers/.keep -------------------------------------------------------------------------------- /app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pgolm/ansible-inventory-manager/670cc22617e919a87b9dd882e34a5f3978fddc39/app/models/.keep -------------------------------------------------------------------------------- /app/models/ability.rb: -------------------------------------------------------------------------------- 1 | class Ability 2 | include CanCan::Ability 3 | 4 | def initialize(user) 5 | user ||= User.new # guest 6 | 7 | # alias 8 | alias_action :create, :read, :update, :destroy, :to => :crud 9 | 10 | if user.is_admin? # admin 11 | can :manage, :all 12 | elsif not user.uid.nil? # user 13 | can :crud, Inventory, user_id: user.id 14 | can :crud, Host 15 | can :crud, Group 16 | can :crud, user 17 | else # guest 18 | can :create, Identity if AnsibleWebInventory::Application.config.registration 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pgolm/ansible-inventory-manager/670cc22617e919a87b9dd882e34a5f3978fddc39/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/concerns/json_validator.rb: -------------------------------------------------------------------------------- 1 | class JsonValidator < ActiveModel::EachValidator 2 | def validate_each(record, attribute, value) 3 | begin 4 | ActiveSupport::JSON.decode(value).to_json unless value.empty? 5 | rescue 6 | begin 7 | YAML::load(value).to_json unless value.empty? 8 | rescue 9 | record.errors[attribute] << 'cannot parse as json or yaml' 10 | end 11 | end 12 | end 13 | end -------------------------------------------------------------------------------- /app/models/concerns/secure_key.rb: -------------------------------------------------------------------------------- 1 | module SecureKey 2 | extend ActiveSupport::Concern 3 | 4 | module ClassMethods 5 | def next_secure_key 6 | SecureRandom.hex 7 | end 8 | end 9 | end -------------------------------------------------------------------------------- /app/models/concerns/variabable.rb: -------------------------------------------------------------------------------- 1 | module Variabable 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | before_save :jsonify_vars 6 | end 7 | 8 | def jsonify_vars 9 | unless variables.nil? 10 | begin 11 | self.variables = ActiveSupport::JSON.decode(variables).to_json 12 | rescue 13 | yaml = YAML::load(variables) 14 | unless yaml 15 | self.variables = "{}" 16 | else 17 | self.variables = yaml.to_json 18 | end 19 | end 20 | end 21 | end 22 | end -------------------------------------------------------------------------------- /app/models/group.rb: -------------------------------------------------------------------------------- 1 | class Group < ActiveRecord::Base 2 | include Variabable 3 | default_scope { order(:name) } 4 | 5 | validates :name, presence: true, 6 | uniqueness: { scope: [:group_id, :inventory_id] }, 7 | exclusion: { in: %w( * all ) } 8 | validates :inventory_id, presence: true 9 | 10 | belongs_to :inventory 11 | 12 | has_many :group_host 13 | has_many :hosts, through: :group_host, source: :host 14 | 15 | def hosts=(host_ids) 16 | hosts.clear 17 | 18 | host_ids.each do |h| 19 | begin 20 | hosts << Host.find(h) 21 | rescue 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/models/group_host.rb: -------------------------------------------------------------------------------- 1 | class GroupHost < ActiveRecord::Base 2 | belongs_to :host 3 | belongs_to :group 4 | end 5 | -------------------------------------------------------------------------------- /app/models/host.rb: -------------------------------------------------------------------------------- 1 | class Host < ActiveRecord::Base 2 | include Variabable 3 | default_scope { order(:alias) } 4 | 5 | validates :alias, presence: true, uniqueness: { scope: :inventory_id } 6 | validates :inventory_id, presence: true 7 | validates :variables, json: true 8 | 9 | belongs_to :inventory 10 | 11 | has_many :group_host 12 | has_many :groups, through: :group_host, source: :group 13 | 14 | def groups=(group_ids) 15 | groups.clear 16 | 17 | group_ids.each do |g| 18 | begin 19 | groups << Group.find(g) 20 | rescue 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/models/identity.rb: -------------------------------------------------------------------------------- 1 | class Identity < OmniAuth::Identity::Models::ActiveRecord 2 | validates_uniqueness_of :email 3 | validates_format_of :email, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/ 4 | end 5 | -------------------------------------------------------------------------------- /app/models/inventory.rb: -------------------------------------------------------------------------------- 1 | class Inventory < ActiveRecord::Base 2 | include SecureKey 3 | 4 | before_create do 5 | self.key = Inventory::next_secure_key 6 | end 7 | 8 | default_scope { order(:name) } 9 | 10 | validates :name, presence: true, uniqueness: { scope: :user_id } 11 | validates :owner, presence: true 12 | 13 | belongs_to :owner, foreign_key: :user_id, class_name: User 14 | has_many :groups, class_name: Group, dependent: :destroy 15 | has_many :hosts, class_name: Host, dependent: :destroy 16 | 17 | def as_ansible_ini 18 | result = "" 19 | 20 | self.hosts.find_each do |host| 21 | result += "#{ini_host_line(host)}\n" 22 | end 23 | 24 | self.groups.find_each do |group| 25 | result += "\n[#{group.name}]\n" 26 | group.hosts.find_each do |host| 27 | result += ini_host_line(host) + "\n" 28 | end 29 | 30 | if group.variables.size > 0 31 | result += "\n[#{group.name}:vars]\n" 32 | vars = ActiveSupport::JSON.decode(group.variables).map {|var| "#{var[0]}=#{var[1]}"} 33 | result += vars.join('\n') 34 | end 35 | end 36 | 37 | result 38 | end 39 | 40 | private 41 | 42 | def ini_host_line(host) 43 | begin 44 | result = host.alias + ' ' 45 | vars = ActiveSupport::JSON.decode(host.variables).map {|var| "#{var[0]}=#{var[1]}"} 46 | result += vars.join(' ') 47 | rescue 48 | "# Error with #{host}" 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | include SecureKey 3 | 4 | default_scope { order :email } 5 | 6 | has_many :inventories, dependent: :destroy 7 | 8 | def self.create_with_omniauth(auth) 9 | create! do |user| 10 | user.provider = auth["provider"] 11 | user.uid = auth["uid"] 12 | user.email = auth["info"]["email"] 13 | end 14 | end 15 | 16 | def identity 17 | if has_identity? 18 | Identity.find_by(email: self.email) 19 | end 20 | end 21 | 22 | def has_identity? 23 | self.provider == 'identity' 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/views/administration/user.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | User 5 | <%= link_to new_identity_path, 6 | class: 'btn btn-primary pull-right' do %> 7 | 8 | <% end %> 9 |

10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | <% @users.each do |user| %> 23 | 24 | 25 | 26 | 34 | 35 | <% end %> 36 | 37 |
EmailCreated
<%= link_to user.email, user %><%= user.created_at %> 27 | <%= link_to user, 28 | method: :delete, 29 | data: { confirm: 'Delete this user?'}, 30 | class: 'btn btn-danger pull-right' do %> 31 | 32 | <% end %> 33 |
38 |
-------------------------------------------------------------------------------- /app/views/groups/_form.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label :name %> 3 | <%= f.text_field :name, class: 'form-control' %> 4 |
5 | 6 |
7 | <%= f.label :description %> 8 | <%= f.text_area :description, class: 'form-control' %> 9 |
10 | 11 |
12 | <%= f.label :hosts %> 13 | <%= f.collection_select :hosts, @group.inventory.hosts, :id, :alias, { selected: @group.host_ids, :include_blank => false }, { class: 'form-control', multiple: true } %> 14 |
15 | 16 |
17 | <%= f.label :variables %> 18 | 19 |
20 | 23 | 26 |
27 | 28 |

29 | 30 | <%= f.text_area :variables, class: 'form-control', id: 'var_area', rows: '10' %> 31 |
-------------------------------------------------------------------------------- /app/views/groups/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= render 'shared/form_errors', errors: @group.errors %> 2 | 3 | <%= form_for [@group.inventory, @group] do |f| %> 4 | <%= render 'form', f: f %> 5 | 6 | <%= render 'shared/form_submit_cancel', f: f, back_path: @group.inventory %> 7 | <% end %> -------------------------------------------------------------------------------- /app/views/groups/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= render 'shared/form_errors', errors: @group.errors %> 2 | 3 | <%= form_for [@inventory, @group] do |f| %> 4 | <%= f.hidden_field :inventory_id %> 5 | <%= render 'form', f: f %> 6 | 7 | <%= render 'shared/form_submit_cancel', f: f, back_path: @inventory %> 8 | <% end %> -------------------------------------------------------------------------------- /app/views/hosts/_form.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label :alias %> 3 | <%= f.text_field :alias, class: 'form-control' %> 4 |
5 | 6 |
7 | <%= f.label :description %> 8 | <%= f.text_area :description, class: 'form-control' %> 9 |
10 | 11 |
12 | <%= f.label :groups %> 13 | <%= f.collection_select :groups, @host.inventory.groups, :id, :name, { selected: @host.group_ids, :include_blank => false }, { class: 'form-control', multiple: true } %> 14 |
15 | 16 |
17 | <%= f.label :variables %> 18 | 19 |
20 | 23 | 26 |
27 | 28 |

29 | 30 | <%= f.text_area :variables, class: 'form-control', id: 'var_area', rows: '10' %> 31 |
32 | 33 | <%= render 'shared/form_submit_cancel', f: f, back_path: @host.inventory %> -------------------------------------------------------------------------------- /app/views/hosts/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= render 'shared/form_errors', errors: @host.errors %> 2 | 3 | <%= form_for [@host.inventory, @host] do |f| %> 4 | <%= render 'form', f: f %> 5 | <% end %> -------------------------------------------------------------------------------- /app/views/hosts/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= render 'shared/form_errors', errors: @host.errors %> 2 | 3 | <%= form_for [@inventory, @host] do |f| %> 4 | <%= f.hidden_field :inventory_id %> 5 | 6 | <%= render 'form', f: f %> 7 | <% end %> -------------------------------------------------------------------------------- /app/views/identities/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= form_tag "/auth/identity/register" do %> 3 | <% if @identity && @identity.errors.any? %> 4 |
5 |

<%= pluralize(@identity.errors.count, "error") %> prohibited this account from being saved:

6 |
    7 | <% @identity.errors.full_messages.each do |msg| %> 8 |
  • <%= msg %>
  • 9 | <% end %> 10 |
11 |
12 | <% end %> 13 |
14 | <%= label_tag :email %>
15 | <%= text_field_tag :email, @identity.try(:email), class: 'form-control' %> 16 |
17 |
18 | <%= label_tag :password %>
19 | <%= password_field_tag :password, nil, class: 'form-control' %> 20 |
21 |
22 | <%= label_tag :password_confirmation %>
23 | <%= password_field_tag :password_confirmation, nil, class: 'form-control' %> 24 |
25 | 26 | <%= submit_tag "Register", class: 'btn btn-primary pull-right' %> 27 | <% end %> 28 |
-------------------------------------------------------------------------------- /app/views/inventories/_form.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label :name %> 3 | <%= f.text_field :name, class: 'form-control' %> 4 |
5 | 6 |
7 | <%= f.label :description %> 8 | <%= f.text_area :description, class: 'form-control', rows: '10' %> 9 |
-------------------------------------------------------------------------------- /app/views/inventories/_groups.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | Groups 5 | <%= link_to new_inventory_group_path(@inventory), 6 | class: 'btn btn-primary pull-right' do %> 7 | 8 | <% end %> 9 |

10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | <% @inventory.groups.find_each do |group| %> 28 | 29 | 30 | 31 | 45 | 46 | <% end %> 47 | 48 |
NameHosts
all<%= @inventory.hosts.size %>
<%= group.name %><%= group.hosts.size %> 32 |
33 | <%= link_to edit_inventory_group_path(@inventory, group), 34 | class: 'btn btn-default' do %> 35 | 36 | <% end %> 37 | <%= link_to inventory_group_path(@inventory, group), 38 | method: :delete, 39 | data: { confirm: 'Delete this group?'}, 40 | class: 'btn btn-danger' do %> 41 | 42 | <% end %> 43 |
44 |
49 |
-------------------------------------------------------------------------------- /app/views/inventories/_hosts.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | Hosts 5 | <%= link_to new_inventory_host_path(@inventory), 6 | class: 'btn btn-primary pull-right' do %> 7 | 8 | <% end %> 9 |

10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | <% @inventory.hosts.each do |host| %> 22 | 23 | 24 | 25 | 39 | 40 | <% end %> 41 | 42 |
AliasGroups
<%= host.alias %><%= host.groups.map(&:name).join(', ') %> 26 |
27 | <%= link_to edit_inventory_host_path(@inventory, host), 28 | class: 'btn btn-default' do %> 29 | 30 | <% end %> 31 | <%= link_to inventory_host_path(@inventory, host), 32 | method: :delete, 33 | data: { confirm: 'Delete this host?'}, 34 | class: 'btn btn-danger' do %> 35 | 36 | <% end %> 37 |
38 |
43 |
-------------------------------------------------------------------------------- /app/views/inventories/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= render 'shared/form_errors', errors: @inventory.errors %> 2 | 3 | <%= form_for @inventory do |f| %> 4 | <%= render 'form', f: f %> 5 | 6 | <%= render 'shared/form_submit_cancel', f: f, back_path: @inventory %> 7 | <% end %> -------------------------------------------------------------------------------- /app/views/inventories/index.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <% if can? :manage, Inventory %> 11 | 12 | <% end %> 13 | 14 | 25 | 26 | 27 | 28 | 29 | 30 | <% @inventories.each do |inventory| %> 31 | 32 | 33 | 34 | 35 | 36 | <% if can? :manage, Inventory %> 37 | 42 | <% end %> 43 | 44 | 56 | 57 | <% end %> 58 | 59 |
NameHostsGroupsOwner 15 |
16 | <% if can? :manage, Inventory %> 17 | 18 | <% end %> 19 | 20 | <%= link_to new_inventory_path, class: 'btn btn-primary', id: 'new_inventory' do %> 21 | 22 | <% end %> 23 |
24 |
<%= link_to inventory.name, inventory %><%= inventory.hosts.size %><%= inventory.groups.size %> 38 | <%= link_to_if current_user != inventory.owner, 39 | inventory.owner.email, 40 | inventory.owner %> 41 | 45 |
46 | <%= link_to edit_inventory_path(inventory), class: 'btn btn-default', id: 'edit_inventory' do %> 47 | 48 | <% end %> 49 | 50 | <%= link_to inventory, method: :delete, data: { confirm: 'Delete this inventory?'}, class: 'btn btn-danger', id: "delete_#{inventory.name}" do %> 51 | 52 | <% end %> 53 | 54 |
55 |
-------------------------------------------------------------------------------- /app/views/inventories/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= render 'shared/form_errors', errors: @inventory.errors %> 2 | 3 | <%= form_for @inventory do |f| %> 4 | <%= render 'form', f: f %> 5 | 6 | <%= render 'shared/form_submit_cancel', f: f , back_path: inventories_path %> 7 | <% end %> -------------------------------------------------------------------------------- /app/views/inventories/show.html.erb: -------------------------------------------------------------------------------- 1 | 19 | 20 |
21 |
22 | <%= label_tag :owner, 'Owner:', class: 'col-lg-2 control-label' %> 23 | 24 |
25 | 26 | <%= @inventory.owner.email %> 27 | 28 |
29 |
30 | 31 |
32 | <%= label_tag :key, 'Key:', class: 'col-lg-2 control-label' %> 33 | 34 |
35 | 36 | <%= @inventory.key %> 37 | 38 |
39 |
40 |
41 | 42 |
43 | 44 |
45 | <%= render 'hosts' %> 46 |
47 |
48 | <%= render 'groups' %> 49 |
-------------------------------------------------------------------------------- /app/views/layouts/_flash.html.erb: -------------------------------------------------------------------------------- 1 | <% flash.each do |key, msg| %> 2 |
3 | 4 | <%= msg %> 5 |
6 | <% end %> -------------------------------------------------------------------------------- /app/views/layouts/_footer.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/layouts/_navigation.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/layouts/_navigation_menu_left.html.erb: -------------------------------------------------------------------------------- 1 | <% if current_user %> 2 |
  • <%= link_to 'Inventories', inventories_path %>
  • 3 |
  • <%= link_to 'Help', help_path %>
  • 4 | <% end %> -------------------------------------------------------------------------------- /app/views/layouts/_navigation_menu_right.html.erb: -------------------------------------------------------------------------------- 1 | <% if current_user %> 2 | 3 | <% if current_user.is_admin? %> 4 |
  • 5 | <%= link_to administration_path do %> 6 | 7 |

    Administration

    8 | <% end %> 9 |
  • 10 | <% end %> 11 | 12 |
  • 13 | <%= link_to signout_path do %> 14 | 15 |

    Logout

    16 | <% end %> 17 |
  • 18 | 19 | 24 | 25 | <% else %> 26 | 27 |
  • 28 | <%= link_to signin_path do %> 29 | 30 |

    Login

    31 | <% end %> 32 |
  • 33 | <% end %> -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Ansible Inventory Manager 5 | <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %> 6 | <%= javascript_include_tag "application", "data-turbolinks-track" => true %> 7 | <%= csrf_meta_tags %> 8 | 9 | 10 |
    11 | <%= render 'layouts/navigation' %> 12 | 13 |
    14 | <%= render 'layouts/flash' %> 15 | <%= yield %> 16 |
    17 |
    18 | <%= render 'layouts/footer' %> 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/views/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <%= form_tag "/auth/identity/callback" do %> 3 |
    4 | <%= label_tag :auth_key, "Email" %>
    5 | <%= text_field_tag :auth_key, nil, class: 'form-control' %> 6 |
    7 |
    8 | <%= label_tag :password %>
    9 | <%= password_field_tag :password, nil, class: 'form-control' %> 10 |
    11 | 12 |
    13 | <%= submit_tag "Login", class: 'btn btn-primary' %> 14 | <% if can? :create, Identity %> 15 | or 16 | <%= link_to "Register", new_identity_path %>. 17 | <% end %> 18 |
    19 | <% end %> 20 |
    -------------------------------------------------------------------------------- /app/views/shared/_form_errors.html.erb: -------------------------------------------------------------------------------- 1 | <% if errors.any? %> 2 |
    3 | Please fix the following <%= pluralize(errors.count, "error") %> : 4 | 5 | 10 |
    11 | <% end %> 12 | -------------------------------------------------------------------------------- /app/views/shared/_form_submit_cancel.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <%= link_to 'Cancel', back_path, class: 'btn btn-default' %> 3 | <%= f.submit class: 'btn btn-primary' %> 4 |
    -------------------------------------------------------------------------------- /app/views/static_pages/help.html.erb: -------------------------------------------------------------------------------- 1 | 4 | 5 |

    API

    6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
    URLDescriptionExample
    /api/v1/inventory/[inventory-key]ansible inventory (Ansible >= 1.0)curl "http://localhost:3000/api/v1/inventory/1234?token=[api-key]" 20 |
    /api/v2/inventory/[inventory-key]ansible inventory (Ansible >= 1.3)curl "http://localhost:3000/api/v1/inventory/1234?token=[api-key]" 26 |
    /api/v1/inventory/[inventory-key]/[host]hostvars (Ansible < 1.3)curl "http://localhost:3000/api/v1/inventory/[inventory-key]/localhost?token=[api-key]"
    35 | 36 |

    Inventory Plugin

    37 | 38 | Download the following inventory-plugin: 39 | 43 | 44 |

    Usage

    45 | Get all hosts with ansible: 46 |
    ansible all -i inventory-manager --list-hosts
    -------------------------------------------------------------------------------- /app/views/user/_change_password.html.erb: -------------------------------------------------------------------------------- 1 | <% if @user.has_identity? %> 2 | <%= form_for @user.identity, html: { class: 'form-horizontal', id: 'change_password' } do |f| %> 3 | <%= render 'shared/form_errors', errors: @user.errors %> 4 | 5 |
    6 | <%= f.label :password, class: 'col-lg-2 control-label' %> 7 | 8 |
    9 | <%= f.password_field :password, class: 'form-control' %> 10 |
    11 |
    12 | 13 |
    14 | <%= f.label :password_confirmation, class: 'col-lg-2 control-label' %> 15 | 16 |
    17 | <%= f.password_field :password_confirmation, class: 'form-control' %> 18 |
    19 |
    20 | 21 | <%= render 'shared/form_submit_cancel', f: f, back_path: root_path %> 22 | <% end %> 23 | <% end %> -------------------------------------------------------------------------------- /app/views/user/show.html.erb: -------------------------------------------------------------------------------- 1 | 7 | 8 |
    9 |
    10 | <%= label_tag :email, 'Mail:', class: 'col-lg-2 control-label' %> 11 | 12 |
    13 |

    <%= @user.email %>

    14 |
    15 |
    16 | 17 |
    18 | <%= label_tag :api_key, 'API Key:', class: 'col-lg-2 control-label' %> 19 | 20 |
    21 | 22 | <%= @user.api_key %> 23 | <%= 'No api key' if @user.api_key.nil? %> 24 | 25 | 26 | <%= form_for @user do |f| %> 27 | <%= f.hidden_field :api_key %> 28 | <%= f.submit 'Regenerate', class: 'btn btn-primary', id: 'api_key_btn' %> 29 | <% end %> 30 |
    31 |
    32 |
    33 | 34 | <%= render 'change_password' %> -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../../config/application', __FILE__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'rspec' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('rspec-core', 'rspec') 17 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | # Pick the frameworks you want: 4 | require "active_record/railtie" 5 | require "action_controller/railtie" 6 | require "action_mailer/railtie" 7 | require "sprockets/railtie" 8 | # require "rails/test_unit/railtie" 9 | 10 | # Require the gems listed in Gemfile, including any gems 11 | # you've limited to :test, :development, or :production. 12 | Bundler.require(:default, Rails.env) 13 | 14 | module AnsibleWebInventory 15 | class Application < Rails::Application 16 | # Settings in config/environments/* take precedence over those specified here. 17 | # Application configuration should go into files in config/initializers 18 | # -- all .rb files in that directory are automatically loaded. 19 | 20 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 21 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 22 | # config.time_zone = 'Central Time (US & Canada)' 23 | 24 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 25 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 26 | # config.i18n.default_locale = :de 27 | 28 | 29 | config.version = "0.0.2 beta" 30 | config.registration = false 31 | 32 | config.paths.add "app/api", glob: "**/*.rb" 33 | config.autoload_paths += Dir["#{Rails.root}/app/api/*"] 34 | end 35 | end -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) 5 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | development: 7 | adapter: sqlite3 8 | database: db/development.sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | # Warning: The database defined as "test" will be erased and 13 | # re-generated from your development database when you run "rake". 14 | # Do not set this db to the same as development or production. 15 | test: 16 | adapter: sqlite3 17 | database: db/test.sqlite3 18 | pool: 5 19 | timeout: 5000 20 | 21 | production: 22 | adapter: postgresql 23 | encoding: unicode 24 | database: ansible_inventory_manager 25 | pool: 5 26 | username: myapp 27 | password: 28 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | AnsibleWebInventory::Application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | AnsibleWebInventory::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | end 30 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | AnsibleWebInventory::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both thread web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. 20 | # config.action_dispatch.rack_cache = true 21 | 22 | # Disable Rails's static asset server (Apache or nginx will already do this). 23 | config.serve_static_assets = false 24 | 25 | # Compress JavaScripts and CSS. 26 | config.assets.js_compressor = :uglifier 27 | # config.assets.css_compressor = :sass 28 | 29 | # Do not fallback to assets pipeline if a precompiled asset is missed. 30 | config.assets.compile = false 31 | 32 | # Generate digests for assets URLs. 33 | config.assets.digest = true 34 | 35 | # Version of your assets, change this if you want to expire all your assets. 36 | config.assets.version = '1.0' 37 | 38 | # Specifies the header that your server uses for sending files. 39 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 40 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 41 | 42 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 43 | # config.force_ssl = true 44 | 45 | # Set to :debug to see everything in the log. 46 | config.log_level = :info 47 | 48 | # Prepend all log lines with the following tags. 49 | # config.log_tags = [ :subdomain, :uuid ] 50 | 51 | # Use a different logger for distributed setups. 52 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 53 | 54 | # Use a different cache store in production. 55 | # config.cache_store = :mem_cache_store 56 | 57 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 58 | # config.action_controller.asset_host = "http://assets.example.com" 59 | 60 | # Precompile additional assets. 61 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 62 | # config.assets.precompile += %w( search.js ) 63 | 64 | # Ignore bad email addresses and do not raise email delivery errors. 65 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 66 | # config.action_mailer.raise_delivery_errors = false 67 | 68 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 69 | # the I18n.default_locale when a translation can not be found). 70 | config.i18n.fallbacks = true 71 | 72 | # Send deprecation notices to registered listeners. 73 | config.active_support.deprecation = :notify 74 | 75 | # Disable automatic flushing of the log to improve performance. 76 | # config.autoflush_log = false 77 | 78 | # Use default logging formatter so that PID and timestamp are not suppressed. 79 | config.log_formatter = ::Logger::Formatter.new 80 | end 81 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | AnsibleWebInventory::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static asset server for tests with Cache-Control for performance. 16 | config.serve_static_assets = true 17 | config.static_cache_control = "public, max-age=3600" 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Print deprecation notices to the stderr. 35 | config.active_support.deprecation = :stderr 36 | end 37 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /config/initializers/omniauth.rb: -------------------------------------------------------------------------------- 1 | OmniAuth.config.logger = Rails.logger 2 | 3 | 4 | Rails.application.config.middleware.use OmniAuth::Builder do 5 | provider :identity, :fields => [:email] 6 | end 7 | -------------------------------------------------------------------------------- /config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure your secret_key_base is kept private 11 | # if you're sharing your code publicly. 12 | AnsibleWebInventory::Application.config.secret_key_base = 'dd5cf96537bb982629e7c67bbf4ac027393621ebadf5582ccd1d33ae4e7c909dec39ecc873d9b5f33f5d2e9fd91eab19b13994bec52c7383afc60b39096a021e' 13 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | AnsibleWebInventory::Application.config.session_store :cookie_store, key: '_ansible-web-inventory_session' 4 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | AnsibleWebInventory::Application.routes.draw do 2 | # Omniauth 3 | match 'auth/:provider/callback', to: 'sessions#create', via: [:get, :post] 4 | match 'auth/failure', to: redirect('/'), via: [:get, :post] 5 | 6 | match 'signin', to: 'sessions#new', as: 'signin', via: [:get, :post] 7 | match 'signout', to: 'sessions#destroy', as: 'signout', via: [:get, :post] 8 | 9 | # Auth 10 | resources :identities 11 | 12 | # Inventory 13 | resources :inventories do 14 | resources :hosts 15 | resources :groups 16 | end 17 | 18 | get '/administration', to: 'administration#user' 19 | 20 | # Ansible API 21 | mount API::API => '/api' 22 | 23 | # User 24 | resources :user 25 | 26 | # statics 27 | get '/help', to: 'static_pages#help' 28 | 29 | # Root 30 | root to: redirect('/inventories') 31 | end 32 | -------------------------------------------------------------------------------- /db/migrate/20130923160656_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration 2 | def change 3 | create_table :users do |t| 4 | t.string :provider, null: false 5 | t.string :uid, null: false 6 | t.string :email, null: false 7 | t.string :api_key 8 | t.boolean :is_admin, default: false, null: false 9 | 10 | t.timestamps 11 | end 12 | 13 | add_index :users, :api_key 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20130923161052_create_identities.rb: -------------------------------------------------------------------------------- 1 | class CreateIdentities < ActiveRecord::Migration 2 | def change 3 | create_table :identities do |t| 4 | t.string :email, null: false 5 | t.string :password_digest, null: false 6 | 7 | t.timestamps 8 | end 9 | 10 | add_index :identities, :email, unique: true 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20130924082325_create_groups.rb: -------------------------------------------------------------------------------- 1 | class CreateGroups < ActiveRecord::Migration 2 | def change 3 | create_table :groups do |t| 4 | t.string :name, null: false 5 | t.text :description 6 | t.text :variables, default: '{}' 7 | 8 | t.belongs_to :group 9 | t.belongs_to :inventory, null: false 10 | 11 | t.timestamps 12 | end 13 | 14 | add_index :groups, [:name, :group_id, :inventory_id], unique: true 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/migrate/20130924082459_create_hosts.rb: -------------------------------------------------------------------------------- 1 | class CreateHosts < ActiveRecord::Migration 2 | def change 3 | create_table :hosts do |t| 4 | t.string :alias, null: false 5 | t.text :description 6 | t.text :variables, default: '{}' 7 | 8 | t.belongs_to :inventory, null: false 9 | 10 | t.timestamps 11 | end 12 | 13 | add_index :hosts, [:alias, :inventory_id], unique: true 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20130924082519_create_inventories.rb: -------------------------------------------------------------------------------- 1 | class CreateInventories < ActiveRecord::Migration 2 | def change 3 | create_table :inventories do |t| 4 | t.string :name, null: false 5 | t.text :description 6 | t.string :key, null: false 7 | 8 | t.belongs_to :user, null: false 9 | 10 | t.timestamps 11 | end 12 | 13 | add_index :inventories, [:name, :user_id], unique: true 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20130925144907_create_group_hosts.rb: -------------------------------------------------------------------------------- 1 | class CreateGroupHosts < ActiveRecord::Migration 2 | def change 3 | create_table :group_hosts do |t| 4 | t.belongs_to :host, null: false 5 | t.belongs_to :group, null: false 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended that you check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(version: 20130925144907) do 15 | 16 | create_table "group_hosts", force: true do |t| 17 | t.integer "host_id", null: false 18 | t.integer "group_id", null: false 19 | t.datetime "created_at" 20 | t.datetime "updated_at" 21 | end 22 | 23 | create_table "groups", force: true do |t| 24 | t.string "name", null: false 25 | t.text "description" 26 | t.text "variables", default: "{}" 27 | t.integer "group_id" 28 | t.integer "inventory_id", null: false 29 | t.datetime "created_at" 30 | t.datetime "updated_at" 31 | end 32 | 33 | add_index "groups", ["name", "group_id", "inventory_id"], name: "index_groups_on_name_and_group_id_and_inventory_id", unique: true 34 | 35 | create_table "hosts", force: true do |t| 36 | t.string "alias", null: false 37 | t.text "description" 38 | t.text "variables", default: "{}" 39 | t.integer "inventory_id", null: false 40 | t.datetime "created_at" 41 | t.datetime "updated_at" 42 | end 43 | 44 | add_index "hosts", ["alias", "inventory_id"], name: "index_hosts_on_alias_and_inventory_id", unique: true 45 | 46 | create_table "identities", force: true do |t| 47 | t.string "email", null: false 48 | t.string "password_digest", null: false 49 | t.datetime "created_at" 50 | t.datetime "updated_at" 51 | end 52 | 53 | add_index "identities", ["email"], name: "index_identities_on_email", unique: true 54 | 55 | create_table "inventories", force: true do |t| 56 | t.string "name", null: false 57 | t.text "description" 58 | t.string "key", null: false 59 | t.integer "user_id", null: false 60 | t.datetime "created_at" 61 | t.datetime "updated_at" 62 | end 63 | 64 | add_index "inventories", ["name", "user_id"], name: "index_inventories_on_name_and_user_id", unique: true 65 | 66 | create_table "users", force: true do |t| 67 | t.string "provider", null: false 68 | t.string "uid", null: false 69 | t.string "email", null: false 70 | t.string "api_key" 71 | t.boolean "is_admin", default: false, null: false 72 | t.datetime "created_at" 73 | t.datetime "updated_at" 74 | end 75 | 76 | add_index "users", ["api_key"], name: "index_users_on_api_key" 77 | 78 | end 79 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | Identity.create(email: 'admin@example.com', 2 | password: 'admin', 3 | password_confirmation: 'admin') 4 | 5 | User.create(provider:"identity", 6 | uid: 1, 7 | email: 'admin@example.com', 8 | is_admin: true) -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pgolm/ansible-inventory-manager/670cc22617e919a87b9dd882e34a5f3978fddc39/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pgolm/ansible-inventory-manager/670cc22617e919a87b9dd882e34a5f3978fddc39/lib/tasks/.keep -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pgolm/ansible-inventory-manager/670cc22617e919a87b9dd882e34a5f3978fddc39/log/.keep -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 48 | 49 | 50 | 51 | 52 |
    53 |

    The page you were looking for doesn't exist.

    54 |

    You may have mistyped the address or the page may have moved.

    55 |
    56 |

    If you are the application owner check the logs for more information.

    57 | 58 | 59 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 48 | 49 | 50 | 51 | 52 |
    53 |

    The change you wanted was rejected.

    54 |

    Maybe you tried to change something you didn't have access to.

    55 |
    56 |

    If you are the application owner check the logs for more information.

    57 | 58 | 59 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 48 | 49 | 50 | 51 | 52 |
    53 |

    We're sorry, but something went wrong.

    54 |
    55 |

    If you are the application owner check the logs for more information.

    56 | 57 | 58 | -------------------------------------------------------------------------------- /public/download/inventory-manager: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import sys 5 | import argparse 6 | import urllib2 7 | import ConfigParser 8 | 9 | server = "" 10 | inventory_key = "" 11 | api_key = "" 12 | 13 | def read_settings(): 14 | ''' Reads the settings from the digital_ocean.ini file ''' 15 | config = ConfigParser.SafeConfigParser() 16 | config.read(os.path.dirname(os.path.realpath(__file__)) + '/inventory-manager.ini') 17 | 18 | section = os.path.basename(__file__) 19 | 20 | # Credentials 21 | if config.has_option(section, 'server'): 22 | global server 23 | server = config.get(section, 'server') 24 | 25 | if config.has_option(section, 'inventory_key'): 26 | global inventory_key 27 | inventory_key = config.get(section, 'inventory_key') 28 | 29 | if config.has_option(section, 'api_key'): 30 | global api_key 31 | api_key = config.get(section, 'api_key') 32 | 33 | def main(argv): 34 | parser = argparse.ArgumentParser() 35 | parser.add_argument('--list', action='store_true') 36 | parser.add_argument('--host') 37 | args = parser.parse_args() 38 | 39 | 40 | if args.list: 41 | read_settings() 42 | 43 | request = urllib2.Request('%s/api/v2/inventory/%s?token=%s' % (server, inventory_key, api_key)) 44 | print urllib2.urlopen(request).read() 45 | sys.exit(0) 46 | elif args.host: 47 | read_settings() 48 | 49 | request = urllib2.Request('%s/api/v1/inventory/%s/%s?token=%s' % (server, inventory_key, args.host, api_key)) 50 | print urllib2.urlopen(request).read() 51 | sys.exit(1) 52 | else: 53 | parser.print_help() 54 | 55 | 56 | if __name__ == "__main__": 57 | main(sys.argv[1:]) -------------------------------------------------------------------------------- /public/download/inventory-manager.ini: -------------------------------------------------------------------------------- 1 | [inventory-manager] 2 | server=http://localhost 3 | inventory_key= 4 | api_key= 5 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pgolm/ansible-inventory-manager/670cc22617e919a87b9dd882e34a5f3978fddc39/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /spec/factories/group.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :group, class: Group do 3 | id { generate(:random_id) } 4 | name { "group#{id}" } 5 | association :inventory, factory: :inventory 6 | 7 | trait :with_hosts do 8 | hosts { create_list(:host, 2, inventory_id: inventory_id) } 9 | end 10 | 11 | factory :group_with_hosts, traits: [:with_hosts] 12 | end 13 | end -------------------------------------------------------------------------------- /spec/factories/host.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :host, class: Host do 3 | id { generate(:random_id) } 4 | self.alias { "host#{id}" } 5 | 6 | trait :with_variables do 7 | variables { "{ 'id': #{id} }" } 8 | end 9 | 10 | factory :host_with_vars, traits: [:with_variables] 11 | end 12 | end -------------------------------------------------------------------------------- /spec/factories/inventory.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :inventory, class: Inventory do 3 | id { generate(:random_id) } 4 | key { generate(:random_id) } 5 | name { "Inventory#{id}" } 6 | owner { create(:user) } 7 | 8 | trait :with_hosts do 9 | hosts { create_list(:host_with_vars, 3, inventory_id: id) } 10 | end 11 | 12 | trait :with_group do 13 | groups { create_list(:group, 1, inventory_id: id) } 14 | end 15 | 16 | trait :with_groups do 17 | groups { create_list(:group, 3, inventory_id: id) } 18 | end 19 | 20 | factory :inventory_with_hosts, traits: [:with_hosts] 21 | factory :inventory_with_group, traits: [:with_group] 22 | factory :inventory_with_groups, traits: [:with_groups] 23 | 24 | factory :inventory_with_groups_hosts, traits: [:with_groups, :with_hosts] 25 | end 26 | 27 | end -------------------------------------------------------------------------------- /spec/factories/user.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :user, class: User do 3 | id { generate(:random_id) } 4 | uid { generate(:random_id) } 5 | provider "identity" 6 | email { "admin#{id}@example.com" } 7 | api_key { generate(:random_id) } 8 | end 9 | 10 | factory :identity, class: Identity do 11 | id { generate(:random_id) } 12 | email { "user#{id}@example.com" } 13 | password { "password#{id}" } 14 | password_confirmation { "#{password}" } 15 | 16 | trait :known do 17 | before :create do |user| 18 | create(:user, uid: user.id, email: user.email) 19 | end 20 | end 21 | 22 | factory :known_identity, traits: [:known] 23 | end 24 | end -------------------------------------------------------------------------------- /spec/factories/utils.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | sequence :random_id do |n| 3 | @random_ids ||= (1..100000).to_a.shuffle; 4 | @random_ids[n] 5 | end 6 | end -------------------------------------------------------------------------------- /spec/features/inventory_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | feature "Inventory" do 4 | let(:user) { create(:known_identity) } 5 | let!(:inventory) { create(:inventory_with_groups_hosts, owner: User.find_by(email: user.email)) } 6 | 7 | before do 8 | set_user_session user 9 | end 10 | 11 | scenario "Overview" do 12 | visit inventories_path 13 | expect(page).to have_text(inventory.name) 14 | end 15 | 16 | context "Add Inventory" do 17 | before do 18 | visit inventories_path 19 | click_link "new_inventory" 20 | end 21 | 22 | scenario "Success" do 23 | fill_in "Name", with: "#{inventory.name}_new" 24 | click_on "Create" 25 | 26 | expect(page).to have_text("Inventory was created.") 27 | end 28 | 29 | scenario "Fail" do 30 | fill_in "Name", with: inventory.name 31 | click_on "Create" 32 | 33 | expect(page).to have_text("has already") 34 | end 35 | end 36 | 37 | scenario "Edit Inventory" do 38 | visit inventories_path 39 | click_link "edit_inventory" 40 | fill_in "Name", with: "#{inventory.name}_edit" 41 | click_on "Update" 42 | 43 | expect(page).to have_text("Inventory was updated.") 44 | end 45 | 46 | scenario "Delete Inventory" do 47 | visit inventories_path 48 | click_link "delete_#{inventory.name}" 49 | 50 | expect(page).to have_text("Inventory was deleted.") 51 | end 52 | end -------------------------------------------------------------------------------- /spec/features/session_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | feature "Session" do 4 | let(:identity) { create(:identity) } 5 | let(:known_identity) { create(:known_identity) } 6 | 7 | context "Login" do 8 | before :each do 9 | OmniAuth.config.test_mode = false 10 | visit "/signin" 11 | fill_in "Email", with: identity.email 12 | end 13 | 14 | after :all do 15 | OmniAuth.config.test_mode = true 16 | end 17 | 18 | scenario "Success" do 19 | fill_in "Password", with: identity.password 20 | click_button "Login" 21 | 22 | expect(page).to have_text("Signed in!") 23 | end 24 | 25 | scenario "Denied" do 26 | fill_in "Password", with: "#{identity.password}_failed" 27 | click_button "Login" 28 | 29 | expect(page).to have_text("not authorized") 30 | end 31 | end 32 | 33 | scenario "Logout" do 34 | set_user_session known_identity 35 | visit "/" 36 | click_link "Logout" 37 | 38 | expect(page).to have_text("not authorized") 39 | end 40 | end -------------------------------------------------------------------------------- /spec/helpers/application_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ApplicationHelper do 4 | describe "#avatar_url" do 5 | let(:user) { build(:user) } 6 | it "returns a gravatar url" do 7 | url = helper.avatar_url user, 120 8 | expect(url).to match("#{Digest::MD5.hexdigest(user.email.downcase)}") 9 | end 10 | 11 | it "contains the size" do 12 | size = rand(1000) 13 | url = helper.avatar_url user, size 14 | expect(url).to match("s=#{size}") 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/models/group_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Group do 4 | let(:inventory) { build(:inventory_with_hosts) } 5 | subject { Group.new name: 'MyGroup', inventory: inventory, hosts: inventory.hosts } 6 | 7 | it { should be_valid } 8 | 9 | describe "#name" do 10 | before do 11 | saved_copy = subject.dup 12 | saved_copy.save 13 | end 14 | 15 | specify { expect(subject).to be_invalid } 16 | end 17 | 18 | describe "#inventory" do 19 | before do 20 | subject.inventory = nil 21 | end 22 | 23 | specify { expect(subject).to be_invalid } 24 | end 25 | 26 | specify { expect(subject.inventory).to eq(inventory) } 27 | specify { expect(subject.hosts.length).to eq inventory.hosts.length } 28 | end 29 | -------------------------------------------------------------------------------- /spec/models/host_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Host do 4 | let(:inventory) { build(:inventory_with_groups) } 5 | subject { Host.new alias: 'Myhost', inventory: inventory, groups: inventory.groups } 6 | 7 | it { should be_valid } 8 | 9 | describe "#name" do 10 | before do 11 | saved_copy = subject.dup 12 | saved_copy.save 13 | end 14 | 15 | specify { expect(subject).to be_invalid } 16 | end 17 | 18 | describe "#inventory" do 19 | before do 20 | subject.inventory = nil 21 | end 22 | 23 | specify { expect(subject).to be_invalid } 24 | end 25 | 26 | specify { expect(subject.inventory).to eq(inventory) } 27 | specify { expect(subject.groups.length).to eq inventory.groups.length } 28 | end 29 | -------------------------------------------------------------------------------- /spec/models/inventory_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Inventory do 4 | let(:admin) { build(:user) } 5 | 6 | subject{ Inventory.new(name: 'Inventar', owner: admin) } 7 | 8 | it { should be_valid } 9 | 10 | it "should have a name" do 11 | subject.name = "" 12 | should be_invalid 13 | end 14 | 15 | describe "#owner" do 16 | before do 17 | subject.owner = nil 18 | end 19 | 20 | it { should be_invalid } 21 | end 22 | 23 | context "#name" do 24 | describe "same user" do 25 | before do 26 | inventory_copy = subject.dup 27 | inventory_copy.owner = admin 28 | inventory_copy.save 29 | end 30 | 31 | it { should be_invalid } 32 | end 33 | 34 | describe "different users" do 35 | before do 36 | inventory_copy = subject.dup 37 | inventory_copy.owner = build(:user) 38 | inventory_copy.save 39 | end 40 | 41 | it { should be_valid } 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/omniauth_helper.rb: -------------------------------------------------------------------------------- 1 | module OmniAuthHelpers 2 | def set_user_session(user) 3 | OmniAuth.config.mock_auth[:identity] = { 4 | "uid" => user.id, 5 | "provider" => 'identity', 6 | "info" => { 7 | "email" => user.email 8 | } 9 | } 10 | visit "/auth/identity" 11 | end 12 | end -------------------------------------------------------------------------------- /spec/requests/api_v1_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe API::API do 4 | 5 | describe "APIv1" do 6 | let(:empty_inventory) { create(:inventory) } 7 | let(:unsafed_inventory) { build(:inventory_with_hosts) } 8 | let(:host_inventory) { create(:inventory_with_hosts) } 9 | let(:group_inventory) { create(:inventory_with_group) } 10 | let(:admin) { create(:user) } 11 | 12 | it "accessed denied" do 13 | get "/api/v1/inventory/#{empty_inventory.key}" 14 | expect(response.status).to eq 401 # unauthorized 15 | end 16 | 17 | describe "GET inventory" do 18 | it "didn't found the inventory" do 19 | get "/api/v1/inventory/#{unsafed_inventory.key}?token=#{admin.api_key}" 20 | 21 | expect(response.status).to eq 404 22 | end 23 | 24 | it "returns an empty inventory" do 25 | @expected = { 26 | all: [] 27 | }.to_json 28 | 29 | get "/api/v1/inventory/#{empty_inventory.key}?token=#{admin.api_key}" 30 | 31 | expect(response.status).to eq 200 32 | expect(response.body).to eq @expected 33 | end 34 | 35 | it "returns a inventory with hosts" do 36 | @expected = { 37 | all: host_inventory.hosts.map(&:alias), 38 | }.to_json 39 | 40 | get "/api/v1/inventory/#{host_inventory.key}?token=#{admin.api_key}" 41 | 42 | expect(response.status).to eq 200 43 | expect(response.body).to eq @expected 44 | end 45 | 46 | it "returns an inventory with groups" do 47 | @expected = { 48 | all: [], 49 | } 50 | group_inventory.groups.each do |group| 51 | @expected[group.name] = {hosts: [], vars:{} } 52 | end 53 | 54 | get "/api/v1/inventory/#{group_inventory.key}?token=#{admin.api_key}" 55 | 56 | expect(response.status).to eq 200 57 | expect(response.body).to eq @expected.to_json 58 | end 59 | end 60 | 61 | describe "GET host" do 62 | it "didn't found the host" do 63 | get "/api/v1/inventory/#{host_inventory.key}/#{unsafed_inventory.hosts[0].alias}?token=#{admin.api_key}" 64 | 65 | expect(response.status).to eq 404 66 | end 67 | 68 | it "return a host" do 69 | host = host_inventory.hosts.sample 70 | get "/api/v1/inventory/#{host_inventory.key}/#{host.alias}?token=#{admin.api_key}" 71 | vars = ActiveSupport::JSON.decode(host.variables) 72 | 73 | expect(response.status).to eq 200 74 | expect(response.body).to eq(vars.to_json) 75 | end 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'coveralls' 2 | Coveralls.wear!('rails') 3 | 4 | # This file is copied to spec/ when you run 'rails generate rspec:install' 5 | ENV["RAILS_ENV"] ||= 'test' 6 | require File.expand_path("../../config/environment", __FILE__) 7 | require 'rspec/rails' 8 | require 'rspec/autorun' 9 | require 'capybara/rails' 10 | require 'capybara/rspec' 11 | 12 | require File.expand_path("../omniauth_helper.rb", __FILE__) 13 | 14 | # Requires supporting ruby files with custom matchers and macros, etc, in 15 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 16 | # run as spec files by default. This means that files in spec/support that end 17 | # in _spec.rb will both be required and run as specs, causing the specs to be 18 | # run twice. It is recommended that you do not name files matching this glob to 19 | # end with _spec.rb. You can configure this pattern with with the --pattern 20 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 21 | Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } 22 | 23 | # Checks for pending migrations before tests are run. 24 | # If you are not using ActiveRecord, you can remove this line. 25 | ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration) 26 | 27 | RSpec.configure do |config| 28 | OmniAuth.config.test_mode = true 29 | config.include OmniAuthHelpers 30 | 31 | # Use FactoryGirl 32 | config.include FactoryGirl::Syntax::Methods 33 | 34 | # Disable rspec should syntax 35 | config.expect_with :rspec do |c| 36 | c.syntax = :expect 37 | end 38 | 39 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 40 | # examples within a transaction, remove the following line or assign false 41 | # instead of true. 42 | config.use_transactional_fixtures = true 43 | 44 | # If true, the base class of anonymous controllers will be inferred 45 | # automatically. This will be the default behavior in future versions of 46 | # rspec-rails. 47 | config.infer_base_class_for_anonymous_controllers = false 48 | 49 | # Run specs in random order to surface order dependencies. If you find an 50 | # order dependency and want to debug it, you can fix the order by providing 51 | # the seed, which is printed after each run. 52 | # --seed 1234 53 | config.order = "random" 54 | end 55 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pgolm/ansible-inventory-manager/670cc22617e919a87b9dd882e34a5f3978fddc39/vendor/assets/javascripts/.keep -------------------------------------------------------------------------------- /vendor/assets/javascripts/js-yaml.js: -------------------------------------------------------------------------------- 1 | var jsyaml=window.jsyaml=function(){require=function(e,t,r){function n(r,a){if(!t[r]){if(!e[r]){var o=typeof require=="function"&&require;if(!a&&o)return o(r,!0);if(i)return i(r,!0);throw new Error("Cannot find module '"+r+"'")}var s=t[r]={exports:{}};e[r][0].call(s.exports,function(t){var i=e[r][1][t];return n(i?i:t)},s,s.exports)}return t[r].exports}var i=typeof require=="function"&&require;for(var a=0;a=at||S===r||w===r||x===r||E===r){return true}}return false}function Tt(e){if(1===e){Ct+=" "}else if(e>1){Ct+=n.repeat("\n",e-1)}}function Mt(t,r){var n,i,a,o,s,l,u,c,p=At,h=Ct;if(S===ft||w===ft||x===ft||E===ft||_===ft||$===ft||Z===ft||ct===ft||ht===ft||L===ft||C===ft||j===ft||k===ft||pt===ft||W===ft||O===ft||I===ft||A===ft||G===ft||et===ft){return false}if(q===ft||F===ft){i=e.charCodeAt(ot+1);if(S===i||w===i||x===i||E===i||r&&(_===i||$===i||Z===i||ct===i||ht===i)){return false}}At=f;Ct="";a=o=ot;s=false;while(ot=t){s=true;continue}else{ot=o;st=l;lt=u;ut=c;ft=e.charCodeAt(ot);break}}if(s){_t(a,o,false);Tt(st-l);a=o=ot;s=false}if(S!==ft&&w!==ft){o=ot+1}ft=e.charCodeAt(++ot)}_t(a,o,false);if(Ct){return true}else{At=p;Ct=h;return false}}function Rt(t){var r,n;if(O!==ft){return false}At=f;Ct="";ft=e.charCodeAt(++ot);r=n=ot;while(ots){s=ut}if(x===ft||E===ft){l+=1;continue}if(utt)&&ott){if(zt(t,m,true,n)){if(f){l=Ct}else{u=Ct}}if(!f){Ut(o,s,l,u);s=l=u=null}Bt(true,-1)}if(ut>t&&ote){f=true}else{return false}}}if(f){while(Yt()||Kt()){if(Bt(true,-1)){l=true;if(ut>e){f=true;s=i}else if(ut===e){f=false;s=i}else{return true}}else{s=false}}}if(s){s=l||n}if(f||m===t){if(h===t||d===t){w=e}else{w=e+1}x=ot-lt;if(f){if(s&&(Ht(x)||Vt(x))||qt(w)){c=true}else{if(a&&Gt(w)||Rt(w)||Wt(w)){c=true}else if(Jt()){c=true;if(null!==It||null!==Lt){jt("alias node should not have any properties")}}else if(Mt(w,h===t)){c=true;if(null===It){It="?"}}if(null!==Lt){kt[Lt]=Ct}}}else{c=s&&Ht(x)}}if(null!==It&&"!"!==It){if("?"===It){if(Y){for(p=0,g=rt.length;p tag; it should be "'+b.kind+'", not "'+At+'"')}if(b.resolver){E=b.resolver(Ct,true);if(o!==E){Ct=E}else{jt("cannot resolve a node with !<"+It+"> explicit tag")}}}else{Nt("unknown tag !<"+It+">")}}return null!==It||null!==Lt||c}function $t(){var r=ot,n,i,a,o=false;xt=null;Et=z;St={};kt={};while(ot0||A!==ft){break}o=true;ft=e.charCodeAt(++ot);n=ot;while(ot1024){u+="? "}u+=G+": ";if(!Z(e,l,false,false)){continue}u+=G;r+=u}q=n;G="{"+r+"}"}function $(e,t,r){var n="",i=q,a=Object.keys(t),o,s,l,u,f,c;for(o=0,s=a.length;o tag resolver accepts not "'+f+'" style')}if(n!==r){W=_(r);G=r}else{if(t){throw new i("cannot represent an object of !<"+u.tag+"> type")}else{continue}}}return true}}return false}function Z(e,t,r,n){q=null;G=t;if(!X(t,false)){X(t,true)}if(r){r=0>D||D>e}if(null!==q&&"?"!==q||2!==P&&e>0){n=false}if("object"===W){if(r&&0!==Object.keys(G).length){$(e,G,n)}else{z(e,G)}}else if("array"===W){if(r&&0!==G.length){J(e,G,n)}else{K(e,G)}}else if("string"===W){if("?"!==q){Y(G)}}else if(B){return false}else{throw new i("unacceptabe kind of an object to dump ("+W+")")}if(null!==q&&"?"!==q){G="!<"+q+"> "+G}return true}if(Z(0,e,true,true)){return G+"\n"}else{return""}}function B(e,t){return P(e,r.extend({schema:o},t))}t.exports.dump=P;t.exports.safeDump=B}()},{"./common":12,"./exception":13,"./schema/default_full":9,"./schema/default_safe":8}],4:[function(e,t,r){"use strict";var n=e("./exception");function i(e,t){t=t||{};this.tag=e;this.loader=t["loader"]||null;this.dumper=t["dumper"]||null;if(null===this.loader&&null===this.dumper){throw new n('Incomplete YAML type definition. "loader" or "dumper" setting must be specified.')}if(null!==this.loader){this.loader=new i.Loader(this.loader)}if(null!==this.dumper){this.dumper=new i.Dumper(this.dumper)}}i.Loader=function o(e){e=e||{};this.kind=e["kind"]||null;this.resolver=e["resolver"]||null;if("string"!==this.kind&&"array"!==this.kind&&"object"!==this.kind){throw new n('Unacceptable "kind" setting of a type loader.')}};function a(e){var t={};if(null!==e){Object.keys(e).forEach(function(r){e[r].forEach(function(e){t[String(e)]=r})})}return t}i.Dumper=function s(e){e=e||{};this.kind=e["kind"]||null;this.defaultStyle=e["defaultStyle"]||null;this.instanceOf=e["instanceOf"]||null;this.predicate=e["predicate"]||null;this.representer=e["representer"]||null;this.styleAliases=a(e["styleAliases"]||null);if("undefined"!==this.kind&&"null"!==this.kind&&"boolean"!==this.kind&&"integer"!==this.kind&&"float"!==this.kind&&"string"!==this.kind&&"array"!==this.kind&&"object"!==this.kind&&"function"!==this.kind){throw new n('Unacceptable "kind" setting of a type dumper.')}};t.exports=i},{"./exception":13}],10:[function(e,t,r){"use strict";var n=e("fs");var i=e("./loader");function a(e,t){var r=n.readFileSync(t,"utf8");e.exports=i.safeLoad(r,{filename:t})}if(undefined!==e.extensions){e.extensions[".yml"]=a;e.extensions[".yaml"]=a}t.exports=e},{fs:14,"./loader":2}],12:[function(e,t,r){"use strict";var n={};function i(e){return undefined===e||null===e}function a(e){return"object"===typeof e&&null!==e}function o(e){if(Array.isArray(e)){return e}else if(i(e)){return[]}else{return[e]}}function s(e,t){var r,n,i,a;if(t){a=Object.keys(t);for(r=0,n=a.length;r0&&-1==="\0\r\n…\u2028\u2029".indexOf(this.buffer.charAt(i-1))){i-=1;if(this.position-i>t/2-1){r=" ... ";i+=5;break}}a="";o=this.position;while(ot/2-1){a=" ... ";o-=5;break}}s=this.buffer.slice(i,o);return n.repeat(" ",e)+r+s+a+"\n"+n.repeat(" ",e+this.position-i+r.length)+"^"};i.prototype.toString=function o(e){var t,r="";if(this.name){r+='in "'+this.name+'" '}r+="at line "+(this.line+1)+", column "+(this.column+1);if(!e){t=this.getSnippet();if(t){r+=":\n"+t}}return r};t.exports=i},{"./common":12}],32:[function(e,t,r){!function(){var r=e("util");var n=e("buffer").Buffer;var i=Array.prototype.slice;function a(e){if(Object.keys)return Object.keys(e);var t=[];for(var r in e){if(Object.prototype.hasOwnProperty.call(e,r)){t.push(r)}}return t}var o=t.exports=f;o.AssertionError=function g(e){this.name="AssertionError";this.message=e.message;this.actual=e.actual;this.expected=e.expected;this.operator=e.operator;var t=e.stackStartFunction||u;if(Error.captureStackTrace){Error.captureStackTrace(this,t)}};r.inherits(o.AssertionError,Error);function s(e,t){if(t===undefined){return""+t}if(typeof t==="number"&&(isNaN(t)||!isFinite(t))){return t.toString()}if(typeof t==="function"||t instanceof RegExp){return t.toString()}return t}function l(e,t){if(typeof e=="string"){return e.length=0;s--){if(r[s]!=n[s])return false}for(s=r.length-1;s>=0;s--){o=r[s];if(!c(e[o],t[o]))return false}return true}o.notDeepEqual=function x(e,t,r){if(c(e,t)){u(e,t,r,"notDeepEqual",o.notDeepEqual)}};o.strictEqual=function E(e,t,r){if(e!==t){u(e,t,r,"===",o.strictEqual)}};o.notStrictEqual=function S(e,t,r){if(e===t){u(e,t,r,"!==",o.notStrictEqual)}};function y(e,t){if(!e||!t){return false}if(t instanceof RegExp){return t.test(e)}else if(e instanceof t){return true}else if(t.call({},e)===true){return true}return false}function m(e,t,r,n){var i;if(typeof r==="string"){n=r;r=null}try{t()}catch(a){i=a}n=(r&&r.name?" ("+r.name+").":".")+(n?" "+n:".");if(e&&!i){u("Missing expected exception"+n)}if(!e&&y(i,r)){u("Got unwanted exception"+n)}if(e&&i&&r&&!y(i,r)||!e&&i){throw i}}o.throws=function(e,t,r){m.apply(this,[true].concat(i.call(arguments)))};o.doesNotThrow=function(e,t,r){m.apply(this,[false].concat(i.call(arguments)))};o.ifError=function(e){if(e){throw e}}}()},{util:33,buffer:34}],16:[function(e,t,r){"use strict";var n=e("../type");t.exports=new n("tag:yaml.org,2002:str",{loader:{kind:"string"}})},{"../type":4}],17:[function(e,t,r){"use strict";var n=e("../type");t.exports=new n("tag:yaml.org,2002:seq",{loader:{kind:"array"}})},{"../type":4}],18:[function(e,t,r){"use strict";var n=e("../type");t.exports=new n("tag:yaml.org,2002:map",{loader:{kind:"object"}})},{"../type":4}],19:[function(e,t,r){"use strict";var n=e("../common").NIL;var i=e("../type");var a={"~":true,"null":true,Null:true,NULL:true};function o(e){return a[e]?null:n}t.exports=new i("tag:yaml.org,2002:null",{loader:{kind:"string",resolver:o},dumper:{kind:"null",defaultStyle:"lowercase",representer:{canonical:function(){return"~"},lowercase:function(){return"null"},uppercase:function(){return"NULL"},camelcase:function(){return"Null"}}}})},{"../common":12,"../type":4}],20:[function(e,t,r){"use strict";var n=e("../common").NIL;var i=e("../type");var a={"true":true,True:true,TRUE:true,"false":false,False:false,FALSE:false};var o={"true":true,True:true,TRUE:true,"false":false,False:false,FALSE:false,y:true,Y:true,yes:true,Yes:true,YES:true,n:false,N:false,no:false,No:false,NO:false,on:true,On:true,ON:true,off:false,Off:false,OFF:false};function s(e,t){if(t){if(o.hasOwnProperty(e)){return o[e]}else{return n}}else{if(a.hasOwnProperty(e)){return a[e]}else{return n}}}t.exports=new i("tag:yaml.org,2002:bool",{loader:{kind:"string",resolver:s},dumper:{kind:"boolean",defaultStyle:"lowercase",representer:{lowercase:function(e){return e?"true":"false"},uppercase:function(e){return e?"TRUE":"FALSE"},camelcase:function(e){return e?"True":"False"}}}})},{"../common":12,"../type":4}],21:[function(e,t,r){"use strict";var n=e("../common").NIL;var i=e("../type");var a=new RegExp("^(?:[-+]?0b[0-1_]+"+"|[-+]?0[0-7_]+"+"|[-+]?(?:0|[1-9][0-9_]*)"+"|[-+]?0x[0-9a-fA-F_]+"+"|[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$");function o(e){var t,r,i,o;if(!a.test(e)){return n}t=e.replace(/_/g,"");r="-"===t[0]?-1:1;o=[];if(0<="+-".indexOf(t[0])){t=t.slice(1)}if("0"===t){return 0}else if(/^0b/.test(t)){return r*parseInt(t.slice(2),2)}else if(/^0x/.test(t)){return r*parseInt(t,16)}else if("0"===t[0]){return r*parseInt(t,8)}else if(0<=t.indexOf(":")){t.split(":").forEach(function(e){o.unshift(parseInt(e,10)) 2 | });t=0;i=1;o.forEach(function(e){t+=e*i;i*=60});return r*t}else{return r*parseInt(t,10)}}t.exports=new i("tag:yaml.org,2002:int",{loader:{kind:"string",resolver:o},dumper:{kind:"integer",defaultStyle:"decimal",representer:{binary:function(e){return"0b"+e.toString(2)},octal:function(e){return"0"+e.toString(8)},decimal:function(e){return e.toString(10)},hexadecimal:function(e){return"0x"+e.toString(16).toUpperCase()}},styleAliases:{binary:[2,"bin"],octal:[8,"oct"],decimal:[10,"dec"],hexadecimal:[16,"hex"]}}})},{"../common":12,"../type":4}],22:[function(e,t,r){"use strict";var n=e("../common").NIL;var i=e("../type");var a=new RegExp("^(?:[-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+][0-9]+)?"+"|\\.[0-9_]+(?:[eE][-+][0-9]+)?"+"|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*"+"|[-+]?\\.(?:inf|Inf|INF)"+"|\\.(?:nan|NaN|NAN))$");function o(e){var t,r,i,o;if(!a.test(e)){return n}t=e.replace(/_/g,"").toLowerCase();r="-"===t[0]?-1:1;o=[];if(0<="+-".indexOf(t[0])){t=t.slice(1)}if(".inf"===t){return 1===r?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY}else if(".nan"===t){return NaN}else if(0<=t.indexOf(":")){t.split(":").forEach(function(e){o.unshift(parseFloat(e,10))});t=0;i=1;o.forEach(function(e){t+=e*i;i*=60});return r*t}else{return r*parseFloat(t,10)}}function s(e,t){if(isNaN(e)){switch(t){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}}else if(Number.POSITIVE_INFINITY===e){switch(t){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}}else if(Number.NEGATIVE_INFINITY===e){switch(t){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}}else{return e.toString(10)}}t.exports=new i("tag:yaml.org,2002:float",{loader:{kind:"string",resolver:o},dumper:{kind:"float",defaultStyle:"lowercase",representer:s}})},{"../common":12,"../type":4}],23:[function(e,t,r){"use strict";var n=e("../common").NIL;var i=e("../type");var a=new RegExp("^([0-9][0-9][0-9][0-9])"+"-([0-9][0-9]?)"+"-([0-9][0-9]?)"+"(?:(?:[Tt]|[ \\t]+)"+"([0-9][0-9]?)"+":([0-9][0-9])"+":([0-9][0-9])"+"(?:\\.([0-9]*))?"+"(?:[ \\t]*(Z|([-+])([0-9][0-9]?)"+"(?::([0-9][0-9]))?))?)?$");function o(e){var t,r,i,o,s,l,u,f=0,c=null,p,h,d;t=a.exec(e);if(null===t){return n}r=+t[1];i=+t[2]-1;o=+t[3];if(!t[4]){return new Date(Date.UTC(r,i,o))}s=+t[4];l=+t[5];u=+t[6];if(t[7]){f=t[7].slice(0,3);while(f.length<3){f+="0"}f=+f}if(t[9]){p=+t[10];h=+(t[11]||0);c=(p*60+h)*6e4;if("-"===t[9]){c=-c}}d=new Date(Date.UTC(r,i,o,s,l,u,f));if(c){d.setTime(d.getTime()-c)}return d}function s(e){return e.toISOString()}t.exports=new i("tag:yaml.org,2002:timestamp",{loader:{kind:"string",resolver:o},dumper:{kind:"object",instanceOf:Date,representer:s}})},{"../common":12,"../type":4}],24:[function(e,t,r){"use strict";var n=e("../common").NIL;var i=e("../type");function a(e){return"<<"===e?e:n}t.exports=new i("tag:yaml.org,2002:merge",{loader:{kind:"string",resolver:a}})},{"../common":12,"../type":4}],25:[function(e,t,r){!function(){"use strict";var r=e("buffer").Buffer;var n=e("../common");var i=n.NIL;var a=e("../type");var o="=";var s=[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,0,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1];var l="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split("");function u(e){var t,n,a=0,l=[],u,f;u=0;f=0;for(a=0;a=8){u-=8;if(o!==e.charAt(a)){l.push(f>>u&255)}f&=(1<>2];t+=l[((e[r+0]&3)<<4)+(e[r+1]>>4)];t+=l[((e[r+1]&15)<<2)+(e[r+2]>>6)];t+=l[e[r+2]&63]}i=e.length%3;if(0!==i){r=e.length-i;t+=l[e[r+0]>>2];if(2===i){t+=l[((e[r+0]&3)<<4)+(e[r+1]>>4)];t+=l[(e[r+1]&15)<<2];t+=o}else{t+=l[(e[r+0]&3)<<4];t+=o+o}}return t}t.exports=new a("tag:yaml.org,2002:binary",{loader:{kind:"string",resolver:u},dumper:{kind:"object",instanceOf:r,representer:f}})}()},{buffer:34,"../common":12,"../type":4}],26:[function(e,t,r){"use strict";var n=e("../common").NIL;var i=e("../type");var a=Object.prototype.hasOwnProperty;var o=Object.prototype.toString;function s(e){var t=[],r,i,s,l,u;for(r=0,i=e.length;r=n[0].length){t=t.slice(1,t.length-n[0].length);i=n[1]}try{return new RegExp(t,i)}catch(a){return r}}function a(e){var t="/"+e.source+"/";if(e.global){t+="g"}if(e.multiline){t+="m"}if(e.ignoreCase){t+="i"}return t}t.exports=new n("tag:yaml.org,2002:js/regexp",{loader:{kind:"string",resolver:i},dumper:{kind:"object",instanceOf:RegExp,representer:a}})}()},{"../../common":12,"../../type":4}],35:[function(e,t,r){r.readIEEE754=function(e,t,r,n,i){var a,o,s=i*8-n-1,l=(1<>1,f=-7,c=r?0:i-1,p=r?1:-1,h=e[t+c];c+=p;a=h&(1<<-f)-1;h>>=-f;f+=s;for(;f>0;a=a*256+e[t+c],c+=p,f-=8);o=a&(1<<-f)-1;a>>=-f;f+=n;for(;f>0;o=o*256+e[t+c],c+=p,f-=8);if(a===0){a=1-u}else if(a===l){return o?NaN:(h?-1:1)*Infinity}else{o=o+Math.pow(2,n);a=a-u}return(h?-1:1)*o*Math.pow(2,a-n)};r.writeIEEE754=function(e,t,r,n,i,a){var o,s,l,u=a*8-i-1,f=(1<>1,p=i===23?Math.pow(2,-24)-Math.pow(2,-77):0,h=n?a-1:0,d=n?-1:1,y=t<0||t===0&&1/t<0?1:0;t=Math.abs(t);if(isNaN(t)||t===Infinity){s=isNaN(t)?1:0;o=f}else{o=Math.floor(Math.log(t)/Math.LN2);if(t*(l=Math.pow(2,-o))<1){o--;l*=2}if(o+c>=1){t+=p/l}else{t+=p*Math.pow(2,1-c)}if(t*l>=2){o++;l/=2}if(o+c>=f){s=0;o=f}else if(o+c>=1){s=(t*l-1)*Math.pow(2,i);o=o+c}else{s=t*Math.pow(2,c-1)*Math.pow(2,i);o=0}}for(;i>=8;e[r+h]=s&255,h+=d,s/=256,i-=8);o=o<0;e[r+h]=o&255,h+=d,o/=256,u-=8);e[r+h-d]|=y*128}},{}],33:[function(e,t,r){var n=e("events");r.isArray=i;r.isDate=function(e){return Object.prototype.toString.call(e)==="[object Date]"};r.isRegExp=function(e){return Object.prototype.toString.call(e)==="[object RegExp]"};r.print=function(){};r.puts=function(){};r.debug=function(){};r.inspect=function(e,t,n,s){var l=[];var u=function(e,t){var r={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[30,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[31,39],yellow:[33,39]};var n={special:"cyan",number:"blue","boolean":"yellow",undefined:"grey","null":"bold",string:"green",date:"magenta",regexp:"red"}[t];if(n){return"["+r[n][0]+"m"+e+"["+r[n][1]+"m"}else{return e}};if(!s){u=function(e,t){return e}}function p(e,n){if(e&&typeof e.inspect==="function"&&e!==r&&!(e.constructor&&e.constructor.prototype===e)){return e.inspect(n)}switch(typeof e){case"undefined":return u("undefined","undefined");case"string":var s="'"+JSON.stringify(e).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return u(s,"string");case"number":return u(""+e,"number");case"boolean":return u(""+e,"boolean")}if(e===null){return u("null","null")}var h=f(e);var d=t?c(e):h;if(typeof e==="function"&&d.length===0){if(a(e)){return u(""+e,"regexp")}else{var y=e.name?": "+e.name:"";return u("[Function"+y+"]","special")}}if(o(e)&&d.length===0){return u(e.toUTCString(),"date")}var m,g,v;if(i(e)){g="Array";v=["[","]"]}else{g="Object";v=["{","}"]}if(typeof e==="function"){var b=e.name?": "+e.name:"";m=a(e)?" "+e:" [Function"+b+"]"}else{m=""}if(o(e)){m=" "+e.toUTCString()}if(d.length===0){return v[0]+m+v[1]}if(n<0){if(a(e)){return u(""+e,"regexp")}else{return u("[Object]","special")}}l.push(e);var w=d.map(function(t){var r,a;if(e.__lookupGetter__){if(e.__lookupGetter__(t)){if(e.__lookupSetter__(t)){a=u("[Getter/Setter]","special")}else{a=u("[Getter]","special")}}else{if(e.__lookupSetter__(t)){a=u("[Setter]","special")}}}if(h.indexOf(t)<0){r="["+t+"]"}if(!a){if(l.indexOf(e[t])<0){if(n===null){a=p(e[t])}else{a=p(e[t],n-1)}if(a.indexOf("\n")>-1){if(i(e)){a=a.split("\n").map(function(e){return" "+e}).join("\n").substr(2)}else{a="\n"+a.split("\n").map(function(e){return" "+e}).join("\n")}}}else{a=u("[Circular]","special")}}if(typeof r==="undefined"){if(g==="Array"&&t.match(/^\d+$/)){return a}r=JSON.stringify(""+t);if(r.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)){r=r.substr(1,r.length-2);r=u(r,"name")}else{r=r.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'");r=u(r,"string")}}return r+": "+a});l.pop();var x=0;var E=w.reduce(function(e,t){x++;if(t.indexOf("\n")>=0)x++;return e+t.length+1},0);if(E>50){w=v[0]+(m===""?"":m+"\n ")+" "+w.join(",\n ")+" "+v[1]}else{w=v[0]+m+" "+w.join(", ")+" "+v[1]}return w}return p(e,typeof n==="undefined"?2:n)};function i(e){return e instanceof Array||Array.isArray(e)||e&&e!==Object.prototype&&i(e.__proto__)}function a(e){return e instanceof RegExp||typeof e==="object"&&Object.prototype.toString.call(e)==="[object RegExp]"}function o(e){if(e instanceof Date)return true;if(typeof e!=="object")return false;var t=Date.prototype&&c(Date.prototype);var r=e.__proto__&&c(e.__proto__);return JSON.stringify(r)===JSON.stringify(t)}function s(e){return e<10?"0"+e.toString(10):e.toString(10)}var l=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function u(){var e=new Date;var t=[s(e.getHours()),s(e.getMinutes()),s(e.getSeconds())].join(":");return[e.getDate(),l[e.getMonth()],t].join(" ")}r.log=function(e){};r.pump=null;var f=Object.keys||function(e){var t=[];for(var r in e)t.push(r);return t};var c=Object.getOwnPropertyNames||function(e){var t=[];for(var r in e){if(Object.hasOwnProperty.call(e,r))t.push(r)}return t};var p=Object.create||function(e,t){var r;if(e===null){r={__proto__:null}}else{if(typeof e!=="object"){throw new TypeError("typeof prototype["+typeof e+"] != 'object'")}var n=function(){};n.prototype=e;r=new n;r.__proto__=e}if(typeof t!=="undefined"&&Object.defineProperties){Object.defineProperties(r,t)}return r};r.inherits=function(e,t){e.super_=t;e.prototype=p(t.prototype,{constructor:{value:e,enumerable:false,writable:true,configurable:true}})};var h=/%[sdj%]/g;r.format=function(e){if(typeof e!=="string"){var t=[];for(var n=0;n=a)return e;switch(e){case"%s":return String(i[n++]);case"%d":return Number(i[n++]);case"%j":return JSON.stringify(i[n++]);default:return e}});for(var s=i[n];n=t.length||a>=e.length)break;t[a+r]=e[a];a++}return a}t.prototype.utf8Write=function(e,r,n){var i,o;return t._charsWritten=l(a(e),this,r,n)};t.prototype.asciiWrite=function(e,r,n){var i,a;return t._charsWritten=l(o(e),this,r,n)};t.prototype.binaryWrite=t.prototype.asciiWrite;t.prototype.base64Write=function(e,r,n){var i,a;return t._charsWritten=l(s(e),this,r,n)};t.prototype.base64Slice=function(t,r){var n=Array.prototype.slice.apply(this,arguments);return e("base64-js").fromByteArray(n)};function u(e){try{return decodeURIComponent(e)}catch(t){return String.fromCharCode(65533)}}t.prototype.utf8Slice=function(){var e=Array.prototype.slice.apply(this,arguments);var t="";var r="";var n=0;while(n"};t.prototype.hexSlice=function(e,t){var r=this.length;if(!e||e<0)e=0;if(!t||t<0||t>r)t=r;var n="";for(var a=e;ai){n=i}}var a=e.length;if(a%2){throw new Error("Invalid hex string")}if(n>a/2){n=a/2}for(var o=0;oa){r=a}}n=String(n||"utf8").toLowerCase();switch(n){case"hex":return this.hexWrite(e,t,r);case"utf8":case"utf-8":return this.utf8Write(e,t,r);case"ascii":return this.asciiWrite(e,t,r);case"binary":return this.binaryWrite(e,t,r);case"base64":return this.base64Write(e,t,r);case"ucs2":case"ucs-2":return this.ucs2Write(e,t,r);default:throw new Error("Unknown encoding")}};t.prototype.slice=function(e,t){if(t===undefined)t=this.length;if(t>this.length){throw new Error("oob")}if(e>t){throw new Error("oob")}return new c(this,t-e,+e)};t.prototype.copy=function(e,t,r,i){var a=[];for(var o=r;othis.length){throw new Error("oob")}if(t>r){throw new Error("oob")}for(var n=t;nc.poolSize){this.parent=new t(this.length);this.offset=0}else{if(!h||h.length-h.used"};c.prototype.get=function _(e){if(e<0||e>=this.length)throw new Error("oob");return this.parent[this.offset+e]};c.prototype.set=function F(e,t){if(e<0||e>=this.length)throw new Error("oob");return this.parent[this.offset+e]=t};c.prototype.write=function(e,r,n,i){if(isFinite(r)){if(!isFinite(n)){i=n;n=undefined}}else{var a=i;i=r;r=n;n=a}r=+r||0;var o=this.length-r;if(!n){n=o}else{n=+n;if(n>o){n=o}}i=String(i||"utf8").toLowerCase();var s;switch(i){case"hex":s=this.parent.hexWrite(e,this.offset+r,n);break;case"utf8":case"utf-8":s=this.parent.utf8Write(e,this.offset+r,n);break;case"ascii":s=this.parent.asciiWrite(e,this.offset+r,n);break;case"binary":s=this.parent.binaryWrite(e,this.offset+r,n);break;case"base64":s=this.parent.base64Write(e,this.offset+r,n);break;case"ucs2":case"ucs-2":s=this.parent.ucs2Write(e,this.offset+r,n);break;default:throw new Error("Unknown encoding")}c._charsWritten=t._charsWritten;return s};c.prototype.toString=function(e,t,r){e=String(e||"utf8").toLowerCase();if(typeof t=="undefined"||t<0){t=0}else if(t>this.length){t=this.length}if(typeof r=="undefined"||r>this.length){r=this.length}else if(r<0){r=0}t=t+this.offset;r=r+this.offset;switch(e){case"hex":return this.parent.hexSlice(t,r);case"utf8":case"utf-8":return this.parent.utf8Slice(t,r);case"ascii":return this.parent.asciiSlice(t,r);case"binary":return this.parent.binarySlice(t,r);case"base64":return this.parent.base64Slice(t,r);case"ucs2":case"ucs-2":return this.parent.ucs2Slice(t,r);default:throw new Error("Unknown encoding")}};c.byteLength=t.byteLength;c.prototype.fill=function U(e,t,r){e||(e=0);t||(t=0);r||(r=this.length);if(typeof e==="string"){e=e.charCodeAt(0)}if(!(typeof e==="number")||isNaN(e)){throw new Error("value is not a number")}if(r=this.length){throw new Error("start out of bounds")}if(r<0||r>this.length){throw new Error("end out of bounds")}return this.parent.fill(e,t+this.offset,r+this.offset)};c.prototype.copy=function(e,t,r,n){var i=this;r||(r=0);n||(n=this.length);t||(t=0);if(n=e.length){throw new Error("targetStart out of bounds")}if(r<0||r>=i.length){throw new Error("sourceStart out of bounds")}if(n<0||n>i.length){throw new Error("sourceEnd out of bounds")}if(n>this.length){n=this.length}if(e.length-tthis.length)throw new Error("oob");if(e>t)throw new Error("oob");return new c(this.parent,t-e,+e+this.offset)};c.prototype.utf8Slice=function(e,t){return this.toString("utf8",e,t)};c.prototype.binarySlice=function(e,t){return this.toString("binary",e,t)};c.prototype.asciiSlice=function(e,t){return this.toString("ascii",e,t)};c.prototype.utf8Write=function(e,t){return this.write(e,t,"utf8")};c.prototype.binaryWrite=function(e,t){return this.write(e,t,"binary")};c.prototype.asciiWrite=function(e,t){return this.write(e,t,"ascii")};c.prototype.readUInt8=function(e,t){var r=this;if(!t){n.ok(e!==undefined&&e!==null,"missing offset");n.ok(e=r.length)return;return r.parent[r.offset+e]};function y(e,t,r,i){var a=0;if(!i){n.ok(typeof r==="boolean","missing or invalid endian");n.ok(t!==undefined&&t!==null,"missing offset");n.ok(t+1=e.length)return 0;if(r){a=e.parent[e.offset+t]<<8;if(t+1=e.length)return 0;if(r){if(t+1>>0)}else{if(t+2>>0)}return a}c.prototype.readUInt32LE=function(e,t){return m(this,e,false,t)};c.prototype.readUInt32BE=function(e,t){return m(this,e,true,t)};c.prototype.readInt8=function(e,t){var r=this;var i;if(!t){n.ok(e!==undefined&&e!==null,"missing offset");n.ok(e=r.length)return;i=r.parent[r.offset+e]&128;if(!i){return r.parent[r.offset+e]}return(255-r.parent[r.offset+e]+1)*-1};function g(e,t,r,i){var a,o;if(!i){n.ok(typeof r==="boolean","missing or invalid endian");n.ok(t!==undefined&&t!==null,"missing offset");n.ok(t+1=0,"specified a negative value for writing an unsigned value");n.ok(e<=t,"value is larger than maximum value for type");n.ok(Math.floor(e)===e,"value has a fractional component")}c.prototype.writeUInt8=function(e,t,r){var i=this;if(!r){n.ok(e!==undefined&&e!==null,"missing value");n.ok(t!==undefined&&t!==null,"missing offset");n.ok(t>>(i?1-o:o)*8}}c.prototype.writeUInt16LE=function(e,t,r){E(this,e,t,false,r)};c.prototype.writeUInt16BE=function(e,t,r){E(this,e,t,true,r)};function S(e,t,r,i,a){if(!a){n.ok(t!==undefined&&t!==null,"missing value");n.ok(typeof i==="boolean","missing or invalid endian");n.ok(r!==undefined&&r!==null,"missing offset");n.ok(r+3>>(i?3-o:o)*8&255}}c.prototype.writeUInt32LE=function(e,t,r){S(this,e,t,false,r)};c.prototype.writeUInt32BE=function(e,t,r){S(this,e,t,true,r)};function k(e,t,r){n.ok(typeof e=="number","cannot write a non-number as a number");n.ok(e<=t,"value larger than maximum allowed value");n.ok(e>=r,"value smaller than minimum allowed value");n.ok(Math.floor(e)===e,"value has a fractional component")}function I(e,t,r){n.ok(typeof e=="number","cannot write a non-number as a number");n.ok(e<=t,"value larger than maximum allowed value");n.ok(e>=r,"value smaller than minimum allowed value")}c.prototype.writeInt8=function(e,t,r){var i=this;if(!r){n.ok(e!==undefined&&e!==null,"missing value");n.ok(t!==undefined&&t!==null,"missing offset");n.ok(t=0){i.writeUInt8(e,t,r)}else{i.writeUInt8(255+e+1,t,r)}};function L(e,t,r,i,a){if(!a){n.ok(t!==undefined&&t!==null,"missing value");n.ok(typeof i==="boolean","missing or invalid endian");n.ok(r!==undefined&&r!==null,"missing offset");n.ok(r+1=0){E(e,t,r,i,a)}else{E(e,65535+t+1,r,i,a)}}c.prototype.writeInt16LE=function(e,t,r){L(this,e,t,false,r)};c.prototype.writeInt16BE=function(e,t,r){L(this,e,t,true,r)};function A(e,t,r,i,a){if(!a){n.ok(t!==undefined&&t!==null,"missing value");n.ok(typeof i==="boolean","missing or invalid endian");n.ok(r!==undefined&&r!==null,"missing offset");n.ok(r+3=0){S(e,t,r,i,a)}else{S(e,4294967295+t+1,r,i,a)}}c.prototype.writeInt32LE=function(e,t,r){A(this,e,t,false,r)};c.prototype.writeInt32BE=function(e,t,r){A(this,e,t,true,r)};function C(t,r,i,a,o){if(!o){n.ok(r!==undefined&&r!==null,"missing value");n.ok(typeof a==="boolean","missing or invalid endian");n.ok(i!==undefined&&i!==null,"missing offset");n.ok(i+30){var t=r.shift();t()}}},true);return function n(e){r.push(e);window.postMessage("process-tick","*")}}return function i(e){setTimeout(e,0)}}();n.title="browser";n.browser=true;n.env={};n.argv=[];n.binding=function(e){throw new Error("process.binding is not supported")};n.cwd=function(){return"/"};n.chdir=function(e){throw new Error("process.chdir is not supported")}},{}],36:[function(e,t,r){!function(e){if(!e.EventEmitter)e.EventEmitter=function(){};var t=r.EventEmitter=e.EventEmitter;var n=typeof Array.isArray==="function"?Array.isArray:function(e){return Object.prototype.toString.call(e)==="[object Array]"};function i(e,t){if(e.indexOf)return e.indexOf(t);for(var r=0;r0&&this._events[e].length>r){this._events[e].warned=true;console.error("(node) warning: possible EventEmitter memory "+"leak detected. %d listeners added. "+"Use emitter.setMaxListeners() to increase limit.",this._events[e].length);console.trace()}}this._events[e].push(t)}else{this._events[e]=[this._events[e],t]}return this};t.prototype.on=t.prototype.addListener;t.prototype.once=function(e,t){var r=this; 3 | r.on(e,function n(){r.removeListener(e,n);t.apply(this,arguments)});return this};t.prototype.removeListener=function(e,t){if("function"!==typeof t){throw new Error("removeListener only takes instances of Function")}if(!this._events||!this._events[e])return this;var r=this._events[e];if(n(r)){var a=i(r,t);if(a<0)return this;r.splice(a,1);if(r.length==0)delete this._events[e]}else if(this._events[e]===t){delete this._events[e]}return this};t.prototype.removeAllListeners=function(e){if(arguments.length===0){this._events={};return this}if(e&&this._events&&this._events[e])this._events[e]=null;return this};t.prototype.listeners=function(e){if(!this._events)this._events={};if(!this._events[e])this._events[e]=[];if(!n(this._events[e])){this._events[e]=[this._events[e]]}return this._events[e]}}(e("__browserify_process"))},{__browserify_process:38}],37:[function(e,t,r){!function(e){"use strict";var r="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";function n(e){var t,n,i,a,o,s;if(e.length%4>0){throw"Invalid string. Length must be a multiple of 4"}o=e.indexOf("=");o=o>0?e.length-o:0;s=[];i=o>0?e.length-4:e.length;for(t=0,n=0;t>16);s.push((a&65280)>>8);s.push(a&255)}if(o===2){a=r.indexOf(e[t])<<2|r.indexOf(e[t+1])>>4;s.push(a&255)}else if(o===1){a=r.indexOf(e[t])<<10|r.indexOf(e[t+1])<<4|r.indexOf(e[t+2])>>2;s.push(a>>8&255);s.push(a&255)}return s}function i(e){var t,n=e.length%3,i="",a,o;function s(e){return r[e>>18&63]+r[e>>12&63]+r[e>>6&63]+r[e&63]}for(t=0,o=e.length-n;t>2];i+=r[a<<4&63];i+="==";break;case 2:a=(e[e.length-2]<<8)+e[e.length-1];i+=r[a>>10];i+=r[a>>4&63];i+=r[a<<2&63];i+="=";break}return i}t.exports.toByteArray=n;t.exports.fromByteArray=i}()},{}],31:[function(e,t,r){"use strict";var n=e("esprima");var i=e("../../common").NIL;var a=e("../../type");function o(e){try{var t="("+e+")",r=n.parse(t,{range:true}),a=[],o;if("Program"!==r.type||1!==r.body.length||"ExpressionStatement"!==r.body[0].type||"FunctionExpression"!==r.body[0].expression.type){return i}r.body[0].expression.params.forEach(function(e){a.push(e.name)});o=r.body[0].expression.body.range;return new Function(a,t.slice(o[0]+1,o[1]-1))}catch(s){return i}}function s(e){return e.toString()}t.exports=new a("tag:yaml.org,2002:js/function",{loader:{kind:"string",resolver:o},dumper:{kind:"function",representer:s}})},{"../../common":12,"../../type":4,esprima:39}],39:[function(e,t,r){!function(){!function(e,t){"use strict";if(typeof define==="function"&&define.amd){define(["exports"],t)}else if(typeof r!=="undefined"){t(r)}else{t(e.esprima={})}}(this,function(e){"use strict";var t,r,n,i,a,o,s,l,u,f,c,p,h,d,y;t={BooleanLiteral:1,EOF:2,Identifier:3,Keyword:4,NullLiteral:5,NumericLiteral:6,Punctuator:7,StringLiteral:8};r={};r[t.BooleanLiteral]="Boolean";r[t.EOF]="";r[t.Identifier]="Identifier";r[t.Keyword]="Keyword";r[t.NullLiteral]="Null";r[t.NumericLiteral]="Numeric";r[t.Punctuator]="Punctuator";r[t.StringLiteral]="String";n={AssignmentExpression:"AssignmentExpression",ArrayExpression:"ArrayExpression",BlockStatement:"BlockStatement",BinaryExpression:"BinaryExpression",BreakStatement:"BreakStatement",CallExpression:"CallExpression",CatchClause:"CatchClause",ConditionalExpression:"ConditionalExpression",ContinueStatement:"ContinueStatement",DoWhileStatement:"DoWhileStatement",DebuggerStatement:"DebuggerStatement",EmptyStatement:"EmptyStatement",ExpressionStatement:"ExpressionStatement",ForStatement:"ForStatement",ForInStatement:"ForInStatement",FunctionDeclaration:"FunctionDeclaration",FunctionExpression:"FunctionExpression",Identifier:"Identifier",IfStatement:"IfStatement",Literal:"Literal",LabeledStatement:"LabeledStatement",LogicalExpression:"LogicalExpression",MemberExpression:"MemberExpression",NewExpression:"NewExpression",ObjectExpression:"ObjectExpression",Program:"Program",Property:"Property",ReturnStatement:"ReturnStatement",SequenceExpression:"SequenceExpression",SwitchStatement:"SwitchStatement",SwitchCase:"SwitchCase",ThisExpression:"ThisExpression",ThrowStatement:"ThrowStatement",TryStatement:"TryStatement",UnaryExpression:"UnaryExpression",UpdateExpression:"UpdateExpression",VariableDeclaration:"VariableDeclaration",VariableDeclarator:"VariableDeclarator",WhileStatement:"WhileStatement",WithStatement:"WithStatement"};i={Data:1,Get:2,Set:4};a={UnexpectedToken:"Unexpected token %0",UnexpectedNumber:"Unexpected number",UnexpectedString:"Unexpected string",UnexpectedIdentifier:"Unexpected identifier",UnexpectedReserved:"Unexpected reserved word",UnexpectedEOS:"Unexpected end of input",NewlineAfterThrow:"Illegal newline after throw",InvalidRegExp:"Invalid regular expression",UnterminatedRegExp:"Invalid regular expression: missing /",InvalidLHSInAssignment:"Invalid left-hand side in assignment",InvalidLHSInForIn:"Invalid left-hand side in for-in",MultipleDefaultsInSwitch:"More than one default clause in switch statement",NoCatchOrFinally:"Missing catch or finally after try",UnknownLabel:"Undefined label '%0'",Redeclaration:"%0 '%1' has already been declared",IllegalContinue:"Illegal continue statement",IllegalBreak:"Illegal break statement",IllegalReturn:"Illegal return statement",StrictModeWith:"Strict mode code may not include a with statement",StrictCatchVariable:"Catch variable may not be eval or arguments in strict mode",StrictVarName:"Variable name may not be eval or arguments in strict mode",StrictParamName:"Parameter name eval or arguments is not allowed in strict mode",StrictParamDupe:"Strict mode function may not have duplicate parameter names",StrictFunctionName:"Function name may not be eval or arguments in strict mode",StrictOctalLiteral:"Octal literals are not allowed in strict mode.",StrictDelete:"Delete of an unqualified identifier in strict mode.",StrictDuplicateProperty:"Duplicate data property in object literal not allowed in strict mode",AccessorDataProperty:"Object literal may not have data and accessor property with the same name",AccessorGetSet:"Object literal may not have multiple get/set accessors with the same name",StrictLHSAssignment:"Assignment to eval or arguments is not allowed in strict mode",StrictLHSPostfix:"Postfix increment/decrement may not have eval or arguments operand in strict mode",StrictLHSPrefix:"Prefix increment/decrement may not have eval or arguments operand in strict mode",StrictReservedWord:"Use of future reserved word in strict mode"};o={NonAsciiIdentifierStart:new RegExp("[ªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԧԱ-Ֆՙա-ևא-תװ-ײؠ-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺࠀ-ࠕࠚࠤࠨࡀ-ࡘࢠࢢ-ࢬऄ-हऽॐक़-ॡॱ-ॷॹ-ॿঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-ళవ-హఽౘౙౠౡಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡೱೲഅ-ഌഎ-ഐഒ-ഺഽൎൠൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ະາຳຽເ-ໄໆໜ-ໟༀཀ-ཇཉ-ཬྈ-ྌက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏼᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᛮ-ᛰᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᢰ-ᣵᤀ-ᤜᥐ-ᥭᥰ-ᥴᦀ-ᦫᧁ-ᧇᨀ-ᨖᨠ-ᩔᪧᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᮺ-ᯥᰀ-ᰣᱍ-ᱏᱚ-ᱽᳩ-ᳬᳮ-ᳱᳵᳶᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℹℼ-ℿⅅ-ⅉⅎⅠ-ↈⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞⸯ々-〇〡-〩〱-〵〸-〼ぁ-ゖゝ-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿌ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙿ-ꚗꚠ-ꛯꜗ-ꜟꜢ-ꞈꞋ-ꞎꞐ-ꞓꞠ-Ɦꟸ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꣲ-ꣷꣻꤊ-ꤥꤰ-ꥆꥠ-ꥼꦄ-ꦲꧏꨀ-ꨨꩀ-ꩂꩄ-ꩋꩠ-ꩶꩺꪀ-ꪯꪱꪵꪶꪹ-ꪽꫀꫂꫛ-ꫝꫠ-ꫪꫲ-ꫴꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꯀ-ꯢ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ]"),NonAsciiIdentifierPart:new RegExp("[ªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮ̀-ʹͶͷͺ-ͽΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁ҃-҇Ҋ-ԧԱ-Ֆՙա-և֑-ׇֽֿׁׂׅׄא-תװ-ײؐ-ؚؠ-٩ٮ-ۓە-ۜ۟-۪ۨ-ۼۿܐ-݊ݍ-ޱ߀-ߵߺࠀ-࠭ࡀ-࡛ࢠࢢ-ࢬࣤ-ࣾऀ-ॣ०-९ॱ-ॷॹ-ॿঁ-ঃঅ-ঌএঐও-নপ-রলশ-হ়-ৄেৈো-ৎৗড়ঢ়য়-ৣ০-ৱਁ-ਃਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹ਼ਾ-ੂੇੈੋ-੍ੑਖ਼-ੜਫ਼੦-ੵઁ-ઃઅ-ઍએ-ઑઓ-નપ-રલળવ-હ઼-ૅે-ૉો-્ૐૠ-ૣ૦-૯ଁ-ଃଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହ଼-ୄେୈୋ-୍ୖୗଡ଼ଢ଼ୟ-ୣ୦-୯ୱஂஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹா-ூெ-ைொ-்ௐௗ௦-௯ఁ-ఃఅ-ఌఎ-ఐఒ-నప-ళవ-హఽ-ౄె-ైొ-్ౕౖౘౙౠ-ౣ౦-౯ಂಃಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹ಼-ೄೆ-ೈೊ-್ೕೖೞೠ-ೣ೦-೯ೱೲംഃഅ-ഌഎ-ഐഒ-ഺഽ-ൄെ-ൈൊ-ൎൗൠ-ൣ൦-൯ൺ-ൿංඃඅ-ඖක-නඳ-රලව-ෆ්ා-ුූෘ-ෟෲෳก-ฺเ-๎๐-๙ກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ູົ-ຽເ-ໄໆ່-ໍ໐-໙ໜ-ໟༀ༘༙༠-༩༹༵༷༾-ཇཉ-ཬཱ-྄྆-ྗྙ-ྼ࿆က-၉ၐ-ႝႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚ፝-፟ᎀ-ᎏᎠ-Ᏼᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᛮ-ᛰᜀ-ᜌᜎ-᜔ᜠ-᜴ᝀ-ᝓᝠ-ᝬᝮ-ᝰᝲᝳក-៓ៗៜ៝០-៩᠋-᠍᠐-᠙ᠠ-ᡷᢀ-ᢪᢰ-ᣵᤀ-ᤜᤠ-ᤫᤰ-᤻᥆-ᥭᥰ-ᥴᦀ-ᦫᦰ-ᧉ᧐-᧙ᨀ-ᨛᨠ-ᩞ᩠-᩿᩼-᪉᪐-᪙ᪧᬀ-ᭋ᭐-᭙᭫-᭳ᮀ-᯳ᰀ-᰷᱀-᱉ᱍ-ᱽ᳐-᳔᳒-ᳶᴀ-ᷦ᷼-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼ‌‍‿⁀⁔ⁱⁿₐ-ₜ⃐-⃥⃜⃡-⃰ℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℹℼ-ℿⅅ-ⅉⅎⅠ-ↈⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯ⵿-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞⷠ-ⷿⸯ々-〇〡-〯〱-〵〸-〼ぁ-ゖ゙゚ゝ-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿌ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘫꙀ-꙯ꙴ-꙽ꙿ-ꚗꚟ-꛱ꜗ-ꜟꜢ-ꞈꞋ-ꞎꞐ-ꞓꞠ-Ɦꟸ-ꠧꡀ-ꡳꢀ-꣄꣐-꣙꣠-ꣷꣻ꤀-꤭ꤰ-꥓ꥠ-ꥼꦀ-꧀ꧏ-꧙ꨀ-ꨶꩀ-ꩍ꩐-꩙ꩠ-ꩶꩺꩻꪀ-ꫂꫛ-ꫝꫠ-ꫯꫲ-꫶ꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꯀ-ꯪ꯬꯭꯰-꯹가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻ︀-️︠-︦︳︴﹍-﹏ﹰ-ﹴﹶ-ﻼ0-9A-Z_a-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ]")};function m(e,t){if(!e){throw new Error("ASSERT: "+t)}}function g(e,t){return s.slice(e,t)}if(typeof"esprima"[0]==="undefined"){g=function xr(e,t){return s.slice(e,t).join("")}}function v(e){return"0123456789".indexOf(e)>=0}function b(e){return"0123456789abcdefABCDEF".indexOf(e)>=0}function w(e){return"01234567".indexOf(e)>=0}function x(e){return e===" "||e===" "||e===" "||e==="\f"||e===" "||e.charCodeAt(0)>=5760&&" ᠎              ".indexOf(e)>=0}function E(e){return e==="\n"||e==="\r"||e==="\u2028"||e==="\u2029"}function S(e){return e==="$"||e==="_"||e==="\\"||e>="a"&&e<="z"||e>="A"&&e<="Z"||e.charCodeAt(0)>=128&&o.NonAsciiIdentifierStart.test(e)}function k(e){return e==="$"||e==="_"||e==="\\"||e>="a"&&e<="z"||e>="A"&&e<="Z"||e>="0"&&e<="9"||e.charCodeAt(0)>=128&&o.NonAsciiIdentifierPart.test(e)}function I(e){switch(e){case"class":case"enum":case"export":case"extends":case"import":case"super":return true}return false}function L(e){switch(e){case"implements":case"interface":case"package":case"private":case"protected":case"public":case"static":case"yield":case"let":return true}return false}function A(e){return e==="eval"||e==="arguments"}function C(e){var t=false;switch(e.length){case 2:t=e==="if"||e==="in"||e==="do";break;case 3:t=e==="var"||e==="for"||e==="new"||e==="try";break;case 4:t=e==="this"||e==="else"||e==="case"||e==="void"||e==="with";break;case 5:t=e==="while"||e==="break"||e==="catch"||e==="throw";break;case 6:t=e==="return"||e==="typeof"||e==="delete"||e==="switch";break;case 7:t=e==="default"||e==="finally";break;case 8:t=e==="function"||e==="continue"||e==="debugger";break;case 10:t=e==="instanceof";break}if(t){return true}switch(e){case"const":return true;case"yield":case"let":return true}if(l&&L(e)){return true}return I(e)}function O(){var e,t,r;t=false;r=false;while(u=p){W({},a.UnexpectedToken,"ILLEGAL")}}else{e=s[u++];if(u>=p){W({},a.UnexpectedToken,"ILLEGAL")}if(e==="*"){e=s[u];if(e==="/"){++u;t=false}}}}else if(e==="/"){e=s[u+1];if(e==="/"){u+=2;r=true}else if(e==="*"){u+=2;t=true;if(u>=p){W({},a.UnexpectedToken,"ILLEGAL")}}else{break}}else if(x(e)){++u}else if(E(e)){++u;if(e==="\r"&&s[u]==="\n"){++u}++f;c=u}else{break}}}function j(e){var t,r,n,i=0;r=e==="u"?4:2;for(t=0;t"&&n===">"&&i===">"){if(a==="="){u+=4;return{type:t.Punctuator,value:">>>=",lineNumber:f,lineStart:c,range:[e,u]}}}if(r==="="&&n==="="&&i==="="){u+=3;return{type:t.Punctuator,value:"===",lineNumber:f,lineStart:c,range:[e,u]}}if(r==="!"&&n==="="&&i==="="){u+=3;return{type:t.Punctuator,value:"!==",lineNumber:f,lineStart:c,range:[e,u]}}if(r===">"&&n===">"&&i===">"){u+=3;return{type:t.Punctuator,value:">>>",lineNumber:f,lineStart:c,range:[e,u]}}if(r==="<"&&n==="<"&&i==="="){u+=3;return{type:t.Punctuator,value:"<<=",lineNumber:f,lineStart:c,range:[e,u]}}if(r===">"&&n===">"&&i==="="){u+=3;return{type:t.Punctuator,value:">>=",lineNumber:f,lineStart:c,range:[e,u]}}if(n==="="){if("<>=!+-*%&|^/".indexOf(r)>=0){u+=2;return{type:t.Punctuator,value:r+n,lineNumber:f,lineStart:c,range:[e,u]}}}if(r===n&&"+-<>&|".indexOf(r)>=0){if("+-<>&|".indexOf(n)>=0){u+=2;return{type:t.Punctuator,value:r+n,lineNumber:f,lineStart:c,range:[e,u]}}}if("[]<>+-*%&|^!~?:=/".indexOf(r)>=0){return{type:t.Punctuator,value:s[u++],lineNumber:f,lineStart:c,range:[e,u]}}}function F(){var e,r,n;n=s[u];m(v(n)||n===".","Numeric literal must start with a decimal digit or a decimal point");r=u;e="";if(n!=="."){e=s[u++];n=s[u];if(e==="0"){if(n==="x"||n==="X"){e+=s[u++];while(u=p){n=""}W({},a.UnexpectedToken,"ILLEGAL")}}if(u=0&&u=p){return{type:t.EOF,lineNumber:f,lineStart:c,range:[u,u]}}r=_();if(typeof r!=="undefined"){return r}e=s[u];if(e==="'"||e==='"'){return U()}if(e==="."||v(e)){return F()}r=N();if(typeof r!=="undefined"){return r}W({},a.UnexpectedToken,"ILLEGAL")}function T(){var e;if(h){u=h.range[1];f=h.lineNumber;c=h.lineStart;e=h;h=null;return e}h=null;return D()}function M(){var e,t,r;if(h!==null){return h}e=u;t=f;r=c;h=D();u=e;f=t;c=r;return h}function R(){var e,t,r,n;e=u;t=f;r=c;O();n=f!==t;u=e;f=t;c=r;return n}function W(e,t){var r,n=Array.prototype.slice.call(arguments,2),i=t.replace(/%(\d)/g,function(e,t){return n[t]||""});if(typeof e.lineNumber==="number"){r=new Error("Line "+e.lineNumber+": "+i);r.index=e.range[0];r.lineNumber=e.lineNumber;r.column=e.range[0]-c+1}else{r=new Error("Line "+f+": "+i);r.index=u;r.lineNumber=f;r.column=u-c+1}throw r}function q(){try{W.apply(null,arguments)}catch(e){if(y.errors){y.errors.push(e)}else{throw e}}}function G(e){if(e.type===t.EOF){W(e,a.UnexpectedEOS)}if(e.type===t.NumericLiteral){W(e,a.UnexpectedNumber)}if(e.type===t.StringLiteral){W(e,a.UnexpectedString)}if(e.type===t.Identifier){W(e,a.UnexpectedIdentifier)}if(e.type===t.Keyword){if(I(e.value)){W(e,a.UnexpectedReserved)}else if(l&&L(e.value)){q(e,a.StrictReservedWord);return}W(e,a.UnexpectedToken,e.value)}W(e,a.UnexpectedToken,e.value)}function H(e){var r=T();if(r.type!==t.Punctuator||r.value!==e){G(r)}}function V(e){var r=T();if(r.type!==t.Keyword||r.value!==e){G(r)}}function Y(e){var r=M();return r.type===t.Punctuator&&r.value===e}function K(e){var r=M();return r.type===t.Keyword&&r.value===e}function J(){var e=M(),r=e.value;if(e.type!==t.Punctuator){return false}return r==="="||r==="*="||r==="/="||r==="%="||r==="+="||r==="-="||r==="<<="||r===">>="||r===">>>="||r==="&="||r==="^="||r==="|="}function z(){var e,r;if(s[u]===";"){T();return}r=f;O();if(f!==r){return}if(Y(";")){T();return}e=M();if(e.type!==t.EOF&&!Y("}")){G(e)}}function $(e){return e.type===n.Identifier||e.type===n.MemberExpression}function X(){var e=[];H("[");while(!Y("]")){if(Y(",")){T();e.push(null)}else{e.push(kt());if(!Y("]")){H(",")}}}H("]");return{type:n.ArrayExpression,elements:e}}function Z(e,t){var r,i;r=l;i=Xt();if(t&&l&&A(e[0].name)){q(t,a.StrictParamName)}l=r;return{type:n.FunctionExpression,id:null,params:e,defaults:[],body:i,rest:null,generator:false,expression:false}}function Q(){var e=T();if(e.type===t.StringLiteral||e.type===t.NumericLiteral){if(l&&e.octal){q(e,a.StrictOctalLiteral)}return ur(e)}return{type:n.Identifier,name:e.value}}function et(){var e,r,i,a;e=M();if(e.type===t.Identifier){i=Q();if(e.value==="get"&&!Y(":")){r=Q();H("(");H(")");return{type:n.Property,key:r,value:Z([]),kind:"get"}}else if(e.value==="set"&&!Y(":")){r=Q();H("(");e=M();if(e.type!==t.Identifier){G(T())}a=[Ct()];H(")");return{type:n.Property,key:r,value:Z(a,e),kind:"set"}}else{H(":");return{type:n.Property,key:i,value:kt(),kind:"init"}}}else if(e.type===t.EOF||e.type===t.Punctuator){G(e)}else{r=Q();H(":");return{type:n.Property,key:r,value:kt(),kind:"init"}}}function tt(){var e=[],t,r,o,s={},u=String;H("{");while(!Y("}")){t=et();if(t.key.type===n.Identifier){r=t.key.name}else{r=u(t.key.value)}o=t.kind==="init"?i.Data:t.kind==="get"?i.Get:i.Set;if(Object.prototype.hasOwnProperty.call(s,r)){if(s[r]===i.Data){if(l&&o===i.Data){q({},a.StrictDuplicateProperty)}else if(o!==i.Data){q({},a.AccessorDataProperty)}}else{if(o===i.Data){q({},a.AccessorDataProperty)}else if(s[r]&o){q({},a.AccessorGetSet)}}s[r]|=o}else{s[r]=o}e.push(t);if(!Y("}")){H(",")}}H("}");return{type:n.ObjectExpression,properties:e}}function rt(){var e;H("(");e=It();H(")");return e}function nt(){var e=M(),r=e.type;if(r===t.Identifier){return{type:n.Identifier,name:T().value}}if(r===t.StringLiteral||r===t.NumericLiteral){if(l&&e.octal){q(e,a.StrictOctalLiteral)}return ur(T())}if(r===t.Keyword){if(K("this")){T();return{type:n.ThisExpression}}if(K("function")){return Qt()}}if(r===t.BooleanLiteral){T();e.value=e.value==="true";return ur(e)}if(r===t.NullLiteral){T();e.value=null;return ur(e)}if(Y("[")){return X()}if(Y("{")){return tt()}if(Y("(")){return rt()}if(Y("/")||Y("/=")){return ur(P())}return G(T())}function it(){var e=[];H("(");if(!Y(")")){while(u>")||Y(">>>")){e={type:n.BinaryExpression,operator:T().value,left:e,right:dt()}}return e}function mt(){var e,t;t=d.allowIn;d.allowIn=true;e=yt();while(Y("<")||Y(">")||Y("<=")||Y(">=")||t&&K("in")||K("instanceof")){e={type:n.BinaryExpression,operator:T().value,left:e,right:yt()}}d.allowIn=t;return e}function gt(){var e=mt();while(Y("==")||Y("!=")||Y("===")||Y("!==")){e={type:n.BinaryExpression,operator:T().value,left:e,right:mt()}}return e}function vt(){var e=gt();while(Y("&")){T();e={type:n.BinaryExpression,operator:"&",left:e,right:gt()}}return e}function bt(){var e=vt();while(Y("^")){T();e={type:n.BinaryExpression,operator:"^",left:e,right:vt()}}return e}function wt(){var e=bt();while(Y("|")){T();e={type:n.BinaryExpression,operator:"|",left:e,right:bt()}}return e}function xt(){var e=wt();while(Y("&&")){T();e={type:n.LogicalExpression,operator:"&&",left:e,right:wt()}}return e}function Et(){var e=xt();while(Y("||")){T();e={type:n.LogicalExpression,operator:"||",left:e,right:xt()}}return e}function St(){var e,t,r;e=Et();if(Y("?")){T();t=d.allowIn;d.allowIn=true;r=kt();d.allowIn=t;H(":");e={type:n.ConditionalExpression,test:e,consequent:r,alternate:kt()}}return e}function kt(){var e,t;e=M();t=St();if(J()){if(!$(t)){W({},a.InvalidLHSInAssignment)}if(l&&t.type===n.Identifier&&A(t.name)){q(e,a.StrictLHSAssignment)}t={type:n.AssignmentExpression,operator:T().value,left:t,right:kt()}}return t}function It(){var e=kt();if(Y(",")){e={type:n.SequenceExpression,expressions:[e]};while(u0){if(y.comments[y.comments.length-1].range[1]>r){return}}y.comments.push({type:e,value:t,range:[r,n],loc:i})}function ir(){var e,t,r,n,i,o;e="";i=false;o=false;while(u=p){o=false;e+=t;r.end={line:f,column:p-c};nr("Line",e,n,p,r)}else{e+=t}}else if(i){if(E(t)){if(t==="\r"&&s[u+1]==="\n"){++u;e+="\r\n"}else{e+=t}++f;++u;c=u;if(u>=p){W({},a.UnexpectedToken,"ILLEGAL")}}else{t=s[u++];if(u>=p){W({},a.UnexpectedToken,"ILLEGAL")}e+=t;if(t==="*"){t=s[u];if(t==="/"){e=e.substr(0,e.length-1);i=false;++u;r.end={line:f,column:u-c};nr("Block",e,n,u,r);e=""}}}}else if(t==="/"){t=s[u+1];if(t==="/"){r={start:{line:f,column:u-c}};n=u;u+=2;o=true;if(u>=p){r.end={line:f,column:u-c};o=false;nr("Line",e,n,u,r)}}else if(t==="*"){n=u;u+=2;i=true;r={start:{line:f,column:u-c-2}};if(u>=p){W({},a.UnexpectedToken,"ILLEGAL")}}else{break}}else if(x(t)){++u}else if(E(t)){++u;if(t==="\r"&&s[u]==="\n"){++u}++f;c=u}else{break}}}function ar(){var e,t,r,n=[];for(e=0;e0){n=y.tokens[y.tokens.length-1];if(n.range[0]===e&&n.type==="Punctuator"){if(n.value==="/"||n.value==="/="){y.tokens.pop()}}}y.tokens.push({type:"RegularExpression",value:r.literal,range:[e,u],loc:t});return r}function lr(){var e,t,r,n=[];for(e=0;e0?1:0;c=0;p=s.length;h=null;d={allowIn:true,labelSet:{},inFunctionBody:false,inIteration:false,inSwitch:false};y={};if(typeof t!=="undefined"){y.range=typeof t.range==="boolean"&&t.range;y.loc=typeof t.loc==="boolean"&&t.loc;y.raw=typeof t.raw==="boolean"&&t.raw;if(typeof t.tokens==="boolean"&&t.tokens){y.tokens=[]}if(typeof t.comment==="boolean"&&t.comment){y.comments=[]}if(typeof t.tolerant==="boolean"&&t.tolerant){y.errors=[]}}if(p>0){if(typeof s[0]==="undefined"){if(e instanceof String){s=e.valueOf()}if(typeof s[0]==="undefined"){s=br(e)}}}gr();try{r=rr();if(typeof y.comments!=="undefined"){ar();r.comments=y.comments}if(typeof y.tokens!=="undefined"){lr();r.tokens=y.tokens}if(typeof y.errors!=="undefined"){r.errors=y.errors}if(y.range||y.loc){r.body=yr(r.body)}}catch(i){throw i}finally{vr();y={}}return r}e.version="1.0.2";e.parse=wr;e.Syntax=function(){var e,t={};if(typeof Object.create==="function"){t=Object.create(null)}for(e in n){if(n.hasOwnProperty(e)){t[e]=n[e]}}if(typeof Object.freeze==="function"){Object.freeze(t)}return t}()})}()},{}]},{},[]);return require("./index")}(); -------------------------------------------------------------------------------- /vendor/assets/javascripts/pace.min.js: -------------------------------------------------------------------------------- 1 | /*! pace 0.4.10 */ 2 | (function(){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L=[].slice,M={}.hasOwnProperty,N=function(a,b){function c(){this.constructor=a}for(var d in b)M.call(b,d)&&(a[d]=b[d]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a},O=[].indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(b in this&&this[b]===a)return b;return-1};q={catchupTime:500,initialRate:.03,minTime:500,ghostTime:250,maxProgressPerFrame:10,easeFactor:1.25,startOnPageLoad:!0,restartOnPushState:!0,restartOnBackboneRoute:!0,target:"body",elements:{checkInterval:100,selectors:["body"]},eventLag:{minSamples:10},ajax:{trackMethods:["GET"],trackWebSockets:!0}},y=function(){var a;return null!=(a="undefined"!=typeof performance&&null!==performance?"function"==typeof performance.now?performance.now():void 0:void 0)?a:+new Date},A=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame,p=window.cancelAnimationFrame||window.mozCancelAnimationFrame,null==A&&(A=function(a){return setTimeout(a,50)},p=function(a){return clearTimeout(a)}),C=function(a){var b,c;return b=y(),c=function(){var d;return d=y()-b,b=y(),a(d,function(){return A(c)})},c()},B=function(){var a,b,c;return c=arguments[0],b=arguments[1],a=3<=arguments.length?L.call(arguments,2):[],"function"==typeof c[b]?c[b].apply(c,a):c[b]},r=function(){var a,b,c,d,e,f,g;for(b=arguments[0],d=2<=arguments.length?L.call(arguments,1):[],f=0,g=d.length;g>f;f++)if(c=d[f])for(a in c)M.call(c,a)&&(e=c[a],null!=b[a]&&"object"==typeof b[a]&&null!=e&&"object"==typeof e?r(b[a],e):b[a]=e);return b},u=function(a,b){var c,d,e;if(null==a&&(a="options"),null==b&&(b=!0),e=document.querySelector("[data-pace-"+a+"]")){if(c=e.getAttribute("data-pace-"+a),!b)return c;try{return JSON.parse(c)}catch(f){return d=f,"undefined"!=typeof console&&null!==console?console.error("Error parsing inline pace options",d):void 0}}},null==window.Pace&&(window.Pace={}),z=Pace.options=r(q,window.paceOptions,u()),b=function(){function a(){this.progress=0}return a.prototype.getElement=function(){var a;return null==this.el&&(this.el=document.createElement("div"),this.el.className="pace pace-active",this.el.innerHTML='
    \n
    \n
    \n
    ',a=document.querySelector(z.target),null!=a.firstChild?a.insertBefore(this.el,a.firstChild):a.appendChild(this.el)),this.el},a.prototype.finish=function(){var a;return a=this.getElement(),a.className=a.className.replace("pace-active",""),a.className+=" pace-inactive"},a.prototype.update=function(a){return this.progress=a,this.render()},a.prototype.destroy=function(){return this.getElement().parentNode.removeChild(this.getElement()),this.el=void 0},a.prototype.render=function(){var a,b;return null==document.querySelector(z.target)?!1:(a=this.getElement(),a.children[0].style.width=""+this.progress+"%",(!this.lastRenderedProgress||0|(this.lastRenderedProgress|0!==this.progress))&&(a.setAttribute("data-progress-text",""+(0|this.progress)+"%"),this.progress>=100?b="99":(b=this.progress<10?"0":"",b+=0|this.progress),a.setAttribute("data-progress",""+b)),this.lastRenderedProgress=this.progress)},a.prototype.done=function(){return this.progress>=100},a}(),g=function(){function a(){this.bindings={}}return a.prototype.trigger=function(a,b){var c,d,e,f,g;if(null!=this.bindings[a]){for(f=this.bindings[a],g=[],d=0,e=f.length;e>d;d++)c=f[d],g.push(c.call(this,b));return g}},a.prototype.on=function(a,b){var c;return null==(c=this.bindings)[a]&&(c[a]=[]),this.bindings[a].push(b)},a}(),I=window.XMLHttpRequest,H=window.XDomainRequest,G=window.WebSocket,s=function(a,b){var c,d,e,f;f=[];for(d in b.prototype)try{e=b.prototype[d],null==a[d]&&"function"!=typeof e?f.push(a[d]=e):f.push(void 0)}catch(g){c=g}return f},h=function(a){function b(){var a,c=this;b.__super__.constructor.apply(this,arguments),a=function(a){var b;return b=a.open,a.open=function(d,e){var f;return f=(null!=d?d:"GET").toUpperCase(),O.call(z.ajax.trackMethods,f)>=0&&c.trigger("request",{type:d,url:e,request:a}),b.apply(a,arguments)}},window.XMLHttpRequest=function(b){var c;return c=new I(b),a(c),c},s(window.XMLHttpRequest,I),null!=H&&(window.XDomainRequest=function(){var b;return b=new H,a(b),b},s(window.XDomainRequest,H)),null!=G&&z.ajax.trackWebSockets&&(window.WebSocket=function(a,b){var d;return d=new G(a,b),c.trigger("request",{type:"socket",url:a,protocols:b,request:d}),d},s(window.WebSocket,G))}return N(b,a),b}(g),x=new h,a=function(){function a(){var a=this;this.elements=[],x.on("request",function(){return a.watch.apply(a,arguments)})}return a.prototype.watch=function(a){var b,c,d;return d=a.type,b=a.request,c="socket"===d?new k(b):new l(b),this.elements.push(c)},a}(),l=function(){function a(a){var b,c,d,e,f,g,h=this;if(this.progress=0,null!=window.ProgressEvent)for(c=null,a.addEventListener("progress",function(a){return h.progress=a.lengthComputable?100*a.loaded/a.total:h.progress+(100-h.progress)/2}),g=["load","abort","timeout","error"],d=0,e=g.length;e>d;d++)b=g[d],a.addEventListener(b,function(){return h.progress=100});else f=a.onreadystatechange,a.onreadystatechange=function(){var b;return 0===(b=a.readyState)||4===b?h.progress=100:3===a.readyState&&(h.progress=50),"function"==typeof f?f.apply(null,arguments):void 0}}return a}(),k=function(){function a(a){var b,c,d,e,f=this;for(this.progress=0,e=["error","open"],c=0,d=e.length;d>c;c++)b=e[c],a.addEventListener(b,function(){return f.progress=100})}return a}(),d=function(){function a(a){var b,c,d,f;for(null==a&&(a={}),this.elements=[],null==a.selectors&&(a.selectors=[]),f=a.selectors,c=0,d=f.length;d>c;c++)b=f[c],this.elements.push(new e(b))}return a}(),e=function(){function a(a){this.selector=a,this.progress=0,this.check()}return a.prototype.check=function(){var a=this;return document.querySelector(this.selector)?this.done():setTimeout(function(){return a.check()},z.elements.checkInterval)},a.prototype.done=function(){return this.progress=100},a}(),c=function(){function a(){var a,b,c=this;this.progress=null!=(b=this.states[document.readyState])?b:100,a=document.onreadystatechange,document.onreadystatechange=function(){return null!=c.states[document.readyState]&&(c.progress=c.states[document.readyState]),"function"==typeof a?a.apply(null,arguments):void 0}}return a.prototype.states={loading:0,interactive:50,complete:100},a}(),f=function(){function a(){var a,b,c,d=this;this.progress=0,a=0,c=0,b=y(),setInterval(function(){var e;return e=y()-b-50,b=y(),a+=(e-a)/15,c++>z.eventLag.minSamples&&Math.abs(a)<3&&(a=0),d.progress=100*(3/(a+3))},50)}return a}(),j=function(){function a(a){this.source=a,this.last=this.sinceLastUpdate=0,this.rate=z.initialRate,this.catchup=0,this.progress=this.lastProgress=0,null!=this.source&&(this.progress=B(this.source,"progress"))}return a.prototype.tick=function(a,b){var c;return null==b&&(b=B(this.source,"progress")),b>=100&&(this.done=!0),b===this.last?this.sinceLastUpdate+=a:(this.sinceLastUpdate&&(this.rate=(b-this.last)/this.sinceLastUpdate),this.catchup=(b-this.progress)/z.catchupTime,this.sinceLastUpdate=0,this.last=b),b>this.progress&&(this.progress+=this.catchup*a),c=1-Math.pow(this.progress/100,z.easeFactor),this.progress+=c*this.rate*a,this.progress=Math.min(this.lastProgress+z.maxProgressPerFrame,this.progress),this.progress=Math.max(0,this.progress),this.progress=Math.min(100,this.progress),this.lastProgress=this.progress,this.progress},a}(),E=null,D=null,n=null,F=null,m=null,o=null,v=function(){return z.restartOnPushState?Pace.restart():void 0},null!=window.history.pushState&&(J=window.history.pushState,window.history.pushState=function(){return v(),J.apply(window.history,arguments)}),null!=window.history.replaceState&&(K=window.history.replaceState,window.history.replaceState=function(){return v(),K.apply(window.history,arguments)}),t=!0,z.restartOnBackboneRoute&&setTimeout(function(){return null!=window.Backbone?Backbone.history.on("route",function(a,b){var c,d,e,f,g;if(d=z.restartOnBackboneRoute){if(t)return t=!1,void 0;if("object"==typeof d){for(g=[],e=0,f=d.length;f>e;e++)if(c=d[e],c===b){Pace.restart();break}return g}return Pace.restart()}}):void 0},0),i={ajax:a,elements:d,document:c,eventLag:f},(w=function(){var a,c,d,e,f,g,h,k,l;for(Pace.sources=E=[],h=["ajax","elements","document","eventLag"],d=0,f=h.length;f>d;d++)c=h[d],z[c]!==!1&&E.push(new i[c](z[c]));for(l=null!=(k=z.extraSources)?k:[],e=0,g=l.length;g>e;e++)a=l[e],E.push(new a(z));return Pace.bar=n=new b,D=[],F=new j})(),Pace.stop=function(){return n.destroy(),o=!0,null!=m&&("function"==typeof p&&p(m),m=null),w()},Pace.restart=function(){return Pace.stop(),Pace.go()},Pace.go=function(){return n.render(),o=!1,m=C(function(a,b){var c,d,e,f,g,h,i,k,l,m,p,q,r,s,t,u,v,w;for(k=100-n.progress,d=r=0,e=!0,h=s=0,u=E.length;u>s;h=++s)for(p=E[h],m=null!=D[h]?D[h]:D[h]=[],g=null!=(w=p.elements)?w:[p],i=t=0,v=g.length;v>t;i=++t)f=g[i],l=null!=m[i]?m[i]:m[i]=new j(f),e&=l.done,l.done||(d++,r+=l.tick(a));return c=r/d,n.update(F.tick(a,c)),q=y(),n.done()||e||o?(n.update(100),setTimeout(function(){return n.finish()},Math.max(z.ghostTime,Math.min(z.minTime,y()-q)))):b()})},Pace.start=function(a){return r(z,a),n.render(),document.querySelector(".pace")?Pace.go():setTimeout(Pace.start,50)},"function"==typeof define&&define.amd?define(function(){return Pace}):"object"==typeof exports?module.exports=Pace:z.startOnPageLoad&&Pace.start()}).call(this); -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pgolm/ansible-inventory-manager/670cc22617e919a87b9dd882e34a5f3978fddc39/vendor/assets/stylesheets/.keep --------------------------------------------------------------------------------