├── .gitignore ├── Gemfile ├── Gemfile.lock ├── Guardfile ├── LICENSE.md ├── README.md ├── Rakefile ├── app ├── assets │ ├── images │ │ └── .keep │ ├── javascripts │ │ ├── application.js │ │ ├── support │ │ │ └── waiting_dialog.js │ │ └── virtual_machines.js │ └── stylesheets │ │ ├── application.css │ │ └── boostrap_sass.scss ├── controllers │ ├── application_controller.rb │ ├── concerns │ │ └── .keep │ ├── login_controller.rb │ └── virtual_machines_controller.rb ├── forms │ ├── login_form.rb │ └── virtual_machine_form.rb ├── helpers │ ├── application_helper.rb │ └── form_helper.rb ├── jobs │ ├── warm_cache_job.rb │ ├── warm_configuration_cache_job.rb │ ├── warm_create_options_cache_job.rb │ ├── warm_datacenters_cache_job.rb │ ├── warm_package_cache_job.rb │ ├── warm_product_categories_cache_job.rb │ └── warm_store_hash_cache_job.rb ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── concerns │ │ └── .keep │ ├── softlayer_connection.rb │ ├── store_hash.rb │ ├── store_hash │ │ ├── pricing.rb │ │ └── region.rb │ ├── virtual_machine.rb │ └── virtual_machine │ │ └── pricing.rb └── views │ ├── application │ └── _navbar.html.erb │ ├── layouts │ ├── application.html.erb │ └── login.html.erb │ ├── login │ └── new.html.erb │ ├── shared │ └── form_helper │ │ └── _error_tag.html.erb │ └── virtual_machines │ ├── _form.html.erb │ ├── _price_box.html.erb │ ├── _vm.html.erb │ ├── destroy.js.erb │ ├── index.html.erb │ ├── new.html.erb │ ├── power_cycle.js.erb │ ├── price_box.js.erb │ └── reboot.js.erb ├── bin ├── bundle ├── rails ├── rake ├── rspec ├── setup └── spring ├── config.ru ├── config ├── application.rb ├── boot.rb ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── cookies_serializer.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── session_store.rb │ ├── simple_form.rb │ ├── simple_form_bootstrap.rb │ ├── softlayer.rb │ └── wrap_parameters.rb ├── locales │ ├── en.yml │ ├── responders.en.yml │ └── simple_form.en.yml ├── routes.rb └── secrets.yml ├── db └── seeds.rb ├── lib ├── application_responder.rb ├── assets │ └── .keep ├── tasks │ ├── .keep │ └── custom_seed.rake └── templates │ └── haml │ └── scaffold │ └── _form.html.haml ├── public ├── 404.html ├── 422.html ├── 500.html ├── favicon.ico └── robots.txt ├── spec ├── rails_helper.rb └── spec_helper.rb ├── test ├── controllers │ └── .keep ├── fixtures │ └── .keep ├── helpers │ └── .keep ├── integration │ └── .keep ├── mailers │ └── .keep ├── models │ └── .keep └── test_helper.rb └── vendor └── assets ├── javascripts └── .keep └── stylesheets └── .keep /.gitignore: -------------------------------------------------------------------------------- 1 | ### Rails ### 2 | *.rbc 3 | capybara-*.html 4 | .rspec 5 | /log 6 | /tmp 7 | /db/*.sqlite3 8 | /db/*.sqlite3-journal 9 | /public/system 10 | /coverage/ 11 | /spec/tmp 12 | **.orig 13 | rerun.txt 14 | pickle-email-*.html 15 | 16 | ## Environment normalisation: 17 | /.bundle 18 | /vendor/bundle 19 | /vendor/cache 20 | 21 | # these should all be checked in to normalise the environment: 22 | # Gemfile.lock, .ruby-version, .ruby-gemset 23 | 24 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 25 | .rvmrc 26 | 27 | # if using bower-rails ignore default bower_components path bower.json files 28 | /vendor/assets/bower_components 29 | *.bowerrc 30 | bower.json 31 | 32 | 33 | ### Ruby ### 34 | *.gem 35 | *.rbc 36 | /.config 37 | /coverage/ 38 | /InstalledFiles 39 | /pkg/ 40 | /spec/reports/ 41 | /test/tmp/ 42 | /test/version_tmp/ 43 | /tmp/ 44 | 45 | ## Specific to RubyMotion: 46 | .dat* 47 | .repl_history 48 | build/ 49 | 50 | ## Documentation cache and generated files: 51 | /.yardoc/ 52 | /_yardoc/ 53 | /doc/ 54 | /rdoc/ 55 | 56 | ## Environment normalisation: 57 | /.bundle/ 58 | /lib/bundler/man/ 59 | 60 | # for a library or gem, you might want to ignore these files since the code is 61 | # intended to run in multiple environments; otherwise, check them in: 62 | # Gemfile.lock 63 | # .ruby-version 64 | # .ruby-gemset 65 | 66 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 67 | .rvmrc 68 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | 4 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 5 | gem 'rails', '4.2.6' 6 | # Use sqlite3 as the database for Active Record 7 | gem 'sqlite3' 8 | # Use SCSS for stylesheets 9 | gem 'sass-rails', '~> 5.0' 10 | # Use Uglifier as compressor for JavaScript assets 11 | gem 'uglifier', '>= 1.3.0' 12 | # Use CoffeeScript for .coffee assets and views 13 | gem 'coffee-rails', '~> 4.1.0' 14 | # See https://github.com/sstephenson/execjs#readme for more supported runtimes 15 | # gem 'therubyracer', platforms: :ruby 16 | 17 | # Use jquery as the JavaScript library 18 | gem 'jquery-rails' 19 | # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks 20 | gem 'turbolinks', '5.0.0.beta2' 21 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 22 | gem 'jbuilder', '~> 2.0' 23 | # bundle exec rake doc:rails generates the API under doc/api. 24 | gem 'sdoc', '~> 0.4.0', group: :doc 25 | 26 | # Use ActiveModel has_secure_password 27 | # gem 'bcrypt', '~> 3.1.7' 28 | 29 | # Use Unicorn as the app server 30 | # gem 'unicorn' 31 | 32 | # Use Capistrano for deployment 33 | # gem 'capistrano-rails', group: :development 34 | 35 | group :development, :test do 36 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 37 | gem 'byebug' 38 | 39 | # Access an IRB console on exception pages or by using <%= console %> in views 40 | gem 'web-console', '~> 2.0' 41 | 42 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 43 | gem 'spring' 44 | end 45 | 46 | gem 'bootstrap-sass', '~> 3.3.6' 47 | gem 'country_select', '~> 2.5' 48 | gem 'devise', '~> 3.5' 49 | gem 'softlayer', '~> 0.0' 50 | gem 'json', '~> 1.8' 51 | gem 'paloma', '~> 5.0' 52 | gem 'reform', '~> 2.1' 53 | gem 'responders', '~> 2.1' 54 | gem 'sidekiq', '~> 4.1' 55 | gem 'simple_form', '~> 3.2' 56 | 57 | group :development do 58 | gem 'better_errors' 59 | gem 'binding_of_caller' 60 | gem 'guard', require: false 61 | gem 'guard-rails', require: false 62 | gem 'guard-bundler', require: false 63 | gem 'guard-livereload', require: false 64 | gem 'guard-rspec', require: false 65 | gem 'letter_opener' 66 | gem 'quiet_assets' 67 | gem 'spring-commands-rspec' 68 | gem 'thin' 69 | # pry stuff 70 | gem 'pry' 71 | gem 'pry-rails' 72 | gem 'pry-doc' 73 | gem 'pry-remote' 74 | gem 'pry-byebug', '3.2.0' 75 | gem 'hirb' 76 | gem 'awesome_print' 77 | end 78 | 79 | group :development, :test do 80 | gem 'rspec-rails' 81 | gem 'shoulda-matchers', require: false 82 | end 83 | 84 | group :test do 85 | gem 'capybara' 86 | gem 'email_spec' 87 | gem 'turnip' 88 | end 89 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actionmailer (4.2.6) 5 | actionpack (= 4.2.6) 6 | actionview (= 4.2.6) 7 | activejob (= 4.2.6) 8 | mail (~> 2.5, >= 2.5.4) 9 | rails-dom-testing (~> 1.0, >= 1.0.5) 10 | actionpack (4.2.6) 11 | actionview (= 4.2.6) 12 | activesupport (= 4.2.6) 13 | rack (~> 1.6) 14 | rack-test (~> 0.6.2) 15 | rails-dom-testing (~> 1.0, >= 1.0.5) 16 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 17 | actionview (4.2.6) 18 | activesupport (= 4.2.6) 19 | builder (~> 3.1) 20 | erubis (~> 2.7.0) 21 | rails-dom-testing (~> 1.0, >= 1.0.5) 22 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 23 | activejob (4.2.6) 24 | activesupport (= 4.2.6) 25 | globalid (>= 0.3.0) 26 | activemodel (4.2.6) 27 | activesupport (= 4.2.6) 28 | builder (~> 3.1) 29 | activerecord (4.2.6) 30 | activemodel (= 4.2.6) 31 | activesupport (= 4.2.6) 32 | arel (~> 6.0) 33 | activesupport (4.2.6) 34 | i18n (~> 0.7) 35 | json (~> 1.7, >= 1.7.7) 36 | minitest (~> 5.1) 37 | thread_safe (~> 0.3, >= 0.3.4) 38 | tzinfo (~> 1.1) 39 | addressable (2.4.0) 40 | akami (1.3.1) 41 | gyoku (>= 0.4.0) 42 | nokogiri 43 | arel (6.0.3) 44 | autoprefixer-rails (6.3.6) 45 | execjs 46 | awesome_print (1.6.1) 47 | axiom-types (0.1.1) 48 | descendants_tracker (~> 0.0.4) 49 | ice_nine (~> 0.11.0) 50 | thread_safe (~> 0.3, >= 0.3.1) 51 | bcrypt (3.1.11) 52 | better_errors (2.1.1) 53 | coderay (>= 1.0.0) 54 | erubis (>= 2.6.6) 55 | rack (>= 0.9.0) 56 | binding_of_caller (0.7.2) 57 | debug_inspector (>= 0.0.1) 58 | bootstrap-sass (3.3.6) 59 | autoprefixer-rails (>= 5.2.1) 60 | sass (>= 3.3.4) 61 | builder (3.2.2) 62 | byebug (5.0.0) 63 | columnize (= 0.9.0) 64 | capybara (2.6.2) 65 | addressable 66 | mime-types (>= 1.16) 67 | nokogiri (>= 1.3.3) 68 | rack (>= 1.0.0) 69 | rack-test (>= 0.5.4) 70 | xpath (~> 2.0) 71 | coderay (1.1.1) 72 | coercible (1.0.0) 73 | descendants_tracker (~> 0.0.1) 74 | coffee-rails (4.1.1) 75 | coffee-script (>= 2.2.0) 76 | railties (>= 4.0.0, < 5.1.x) 77 | coffee-script (2.4.1) 78 | coffee-script-source 79 | execjs 80 | coffee-script-source (1.10.0) 81 | columnize (0.9.0) 82 | concurrent-ruby (1.0.1) 83 | connection_pool (2.2.0) 84 | countries (1.2.5) 85 | currencies (~> 0.4.2) 86 | i18n_data (~> 0.7.0) 87 | country_select (2.5.2) 88 | countries (~> 1.2.0) 89 | sort_alphabetical (~> 1.0) 90 | currencies (0.4.2) 91 | daemons (1.2.3) 92 | debug_inspector (0.0.2) 93 | declarative (0.0.7) 94 | uber (>= 0.0.15) 95 | descendants_tracker (0.0.4) 96 | thread_safe (~> 0.3, >= 0.3.1) 97 | devise (3.5.6) 98 | bcrypt (~> 3.0) 99 | orm_adapter (~> 0.1) 100 | railties (>= 3.2.6, < 5) 101 | responders 102 | thread_safe (~> 0.1) 103 | warden (~> 1.2.3) 104 | diff-lcs (1.2.5) 105 | disposable (0.2.6) 106 | declarative (~> 0.0.6) 107 | representable (>= 2.4.0, <= 3.1.0) 108 | uber 109 | em-websocket (0.5.1) 110 | eventmachine (>= 0.12.9) 111 | http_parser.rb (~> 0.6.0) 112 | email_spec (2.0.0) 113 | htmlentities (~> 4.3.3) 114 | launchy (~> 2.1) 115 | mail (~> 2.6.3) 116 | equalizer (0.0.11) 117 | erubis (2.7.0) 118 | eventmachine (1.2.0.1) 119 | excon (0.49.0) 120 | execjs (2.6.0) 121 | ffi (1.9.10) 122 | formatador (0.2.5) 123 | gherkin (2.12.2) 124 | multi_json (~> 1.3) 125 | globalid (0.3.6) 126 | activesupport (>= 4.1.0) 127 | guard (2.13.0) 128 | formatador (>= 0.2.4) 129 | listen (>= 2.7, <= 4.0) 130 | lumberjack (~> 1.0) 131 | nenv (~> 0.1) 132 | notiffany (~> 0.0) 133 | pry (>= 0.9.12) 134 | shellany (~> 0.0) 135 | thor (>= 0.18.1) 136 | guard-bundler (2.1.0) 137 | bundler (~> 1.0) 138 | guard (~> 2.2) 139 | guard-compat (~> 1.1) 140 | guard-compat (1.2.1) 141 | guard-livereload (2.5.2) 142 | em-websocket (~> 0.5) 143 | guard (~> 2.8) 144 | guard-compat (~> 1.0) 145 | multi_json (~> 1.8) 146 | guard-rails (0.7.2) 147 | guard (~> 2.11) 148 | guard-compat (~> 1.0) 149 | guard-rspec (4.6.5) 150 | guard (~> 2.1) 151 | guard-compat (~> 1.1) 152 | rspec (>= 2.99.0, < 4.0) 153 | gyoku (1.3.1) 154 | builder (>= 2.1.2) 155 | hashie (3.4.3) 156 | hirb (0.7.3) 157 | htmlentities (4.3.4) 158 | http_parser.rb (0.6.0) 159 | httpi (2.4.1) 160 | rack 161 | i18n (0.7.0) 162 | i18n_data (0.7.0) 163 | ice_nine (0.11.2) 164 | jbuilder (2.4.1) 165 | activesupport (>= 3.0.0, < 5.1) 166 | multi_json (~> 1.2) 167 | jquery-rails (4.1.1) 168 | rails-dom-testing (>= 1, < 3) 169 | railties (>= 4.2.0) 170 | thor (>= 0.14, < 2.0) 171 | json (1.8.3) 172 | launchy (2.4.3) 173 | addressable (~> 2.3) 174 | letter_opener (1.4.1) 175 | launchy (~> 2.2) 176 | listen (3.0.6) 177 | rb-fsevent (>= 0.9.3) 178 | rb-inotify (>= 0.9.7) 179 | loofah (2.0.3) 180 | nokogiri (>= 1.5.9) 181 | lumberjack (1.0.10) 182 | mail (2.6.4) 183 | mime-types (>= 1.16, < 4) 184 | method_source (0.8.2) 185 | mime-types (3.0) 186 | mime-types-data (~> 3.2015) 187 | mime-types-data (3.2016.0221) 188 | mini_portile2 (2.0.0) 189 | minitest (5.8.4) 190 | multi_json (1.11.2) 191 | nenv (0.3.0) 192 | nokogiri (1.6.7.2) 193 | mini_portile2 (~> 2.0.0.rc2) 194 | nori (2.6.0) 195 | notiffany (0.0.8) 196 | nenv (~> 0.1) 197 | shellany (~> 0.0) 198 | orm_adapter (0.5.0) 199 | paloma (5.0.0) 200 | pry (0.10.3) 201 | coderay (~> 1.1.0) 202 | method_source (~> 0.8.1) 203 | slop (~> 3.4) 204 | pry-byebug (3.2.0) 205 | byebug (~> 5.0) 206 | pry (~> 0.10) 207 | pry-doc (0.8.0) 208 | pry (~> 0.9) 209 | yard (~> 0.8) 210 | pry-rails (0.3.4) 211 | pry (>= 0.9.10) 212 | pry-remote (0.1.8) 213 | pry (~> 0.9) 214 | slop (~> 3.0) 215 | quiet_assets (1.1.0) 216 | railties (>= 3.1, < 5.0) 217 | rack (1.6.4) 218 | rack-test (0.6.3) 219 | rack (>= 1.0) 220 | rails (4.2.6) 221 | actionmailer (= 4.2.6) 222 | actionpack (= 4.2.6) 223 | actionview (= 4.2.6) 224 | activejob (= 4.2.6) 225 | activemodel (= 4.2.6) 226 | activerecord (= 4.2.6) 227 | activesupport (= 4.2.6) 228 | bundler (>= 1.3.0, < 2.0) 229 | railties (= 4.2.6) 230 | sprockets-rails 231 | rails-deprecated_sanitizer (1.0.3) 232 | activesupport (>= 4.2.0.alpha) 233 | rails-dom-testing (1.0.7) 234 | activesupport (>= 4.2.0.beta, < 5.0) 235 | nokogiri (~> 1.6.0) 236 | rails-deprecated_sanitizer (>= 1.0.1) 237 | rails-html-sanitizer (1.0.3) 238 | loofah (~> 2.0) 239 | railties (4.2.6) 240 | actionpack (= 4.2.6) 241 | activesupport (= 4.2.6) 242 | rake (>= 0.8.7) 243 | thor (>= 0.18.1, < 2.0) 244 | rake (11.1.2) 245 | rb-fsevent (0.9.7) 246 | rb-inotify (0.9.7) 247 | ffi (>= 0.5.0) 248 | rdoc (4.2.2) 249 | json (~> 1.4) 250 | redis (3.2.2) 251 | reform (2.1.0) 252 | disposable (>= 0.2.2, < 0.3.0) 253 | representable (>= 2.4.0, < 3.1.0) 254 | uber (~> 0.0.11) 255 | representable (2.4.1) 256 | declarative (~> 0.0.5) 257 | uber (~> 0.0.15) 258 | responders (2.1.2) 259 | railties (>= 4.2.0, < 5.1) 260 | rspec (3.4.0) 261 | rspec-core (~> 3.4.0) 262 | rspec-expectations (~> 3.4.0) 263 | rspec-mocks (~> 3.4.0) 264 | rspec-core (3.4.4) 265 | rspec-support (~> 3.4.0) 266 | rspec-expectations (3.4.0) 267 | diff-lcs (>= 1.2.0, < 2.0) 268 | rspec-support (~> 3.4.0) 269 | rspec-mocks (3.4.1) 270 | diff-lcs (>= 1.2.0, < 2.0) 271 | rspec-support (~> 3.4.0) 272 | rspec-rails (3.4.2) 273 | actionpack (>= 3.0, < 4.3) 274 | activesupport (>= 3.0, < 4.3) 275 | railties (>= 3.0, < 4.3) 276 | rspec-core (~> 3.4.0) 277 | rspec-expectations (~> 3.4.0) 278 | rspec-mocks (~> 3.4.0) 279 | rspec-support (~> 3.4.0) 280 | rspec-support (3.4.1) 281 | sass (3.4.22) 282 | sass-rails (5.0.4) 283 | railties (>= 4.0.0, < 5.0) 284 | sass (~> 3.1) 285 | sprockets (>= 2.8, < 4.0) 286 | sprockets-rails (>= 2.0, < 4.0) 287 | tilt (>= 1.1, < 3) 288 | savon (2.11.1) 289 | akami (~> 1.2) 290 | builder (>= 2.1.2) 291 | gyoku (~> 1.2) 292 | httpi (~> 2.3) 293 | nokogiri (>= 1.4.0) 294 | nori (~> 2.4) 295 | wasabi (~> 3.4) 296 | sdoc (0.4.1) 297 | json (~> 1.7, >= 1.7.7) 298 | rdoc (~> 4.0) 299 | shellany (0.0.1) 300 | shoulda-matchers (3.1.1) 301 | activesupport (>= 4.0.0) 302 | sidekiq (4.1.1) 303 | concurrent-ruby (~> 1.0) 304 | connection_pool (~> 2.2, >= 2.2.0) 305 | redis (~> 3.2, >= 3.2.1) 306 | simple_form (3.2.1) 307 | actionpack (> 4, < 5.1) 308 | activemodel (> 4, < 5.1) 309 | slop (3.6.0) 310 | softlayer (0.0.8) 311 | excon (~> 0.45) 312 | hashie (~> 3.4) 313 | representable (~> 2.3) 314 | savon (~> 2.11) 315 | virtus (~> 1.0) 316 | sort_alphabetical (1.0.2) 317 | unicode_utils (>= 1.2.2) 318 | spring (1.6.4) 319 | spring-commands-rspec (1.0.4) 320 | spring (>= 0.9.1) 321 | sprockets (3.5.2) 322 | concurrent-ruby (~> 1.0) 323 | rack (> 1, < 3) 324 | sprockets-rails (3.0.4) 325 | actionpack (>= 4.0) 326 | activesupport (>= 4.0) 327 | sprockets (>= 3.0.0) 328 | sqlite3 (1.3.11) 329 | thin (1.6.4) 330 | daemons (~> 1.0, >= 1.0.9) 331 | eventmachine (~> 1.0, >= 1.0.4) 332 | rack (~> 1.0) 333 | thor (0.19.1) 334 | thread_safe (0.3.5) 335 | tilt (2.0.2) 336 | turbolinks (5.0.0.beta2) 337 | turbolinks-source 338 | turbolinks-source (5.0.0.beta4) 339 | turnip (2.1.0) 340 | gherkin (~> 2.5) 341 | rspec (>= 3.0, < 4.0) 342 | tzinfo (1.2.2) 343 | thread_safe (~> 0.1) 344 | uber (0.0.15) 345 | uglifier (3.0.0) 346 | execjs (>= 0.3.0, < 3) 347 | unicode_utils (1.4.0) 348 | virtus (1.0.5) 349 | axiom-types (~> 0.1) 350 | coercible (~> 1.0) 351 | descendants_tracker (~> 0.0, >= 0.0.3) 352 | equalizer (~> 0.0, >= 0.0.9) 353 | warden (1.2.6) 354 | rack (>= 1.0) 355 | wasabi (3.5.0) 356 | httpi (~> 2.0) 357 | nokogiri (>= 1.4.2) 358 | web-console (2.3.0) 359 | activemodel (>= 4.0) 360 | binding_of_caller (>= 0.7.2) 361 | railties (>= 4.0) 362 | sprockets-rails (>= 2.0, < 4.0) 363 | xpath (2.0.0) 364 | nokogiri (~> 1.3) 365 | yard (0.8.7.6) 366 | 367 | PLATFORMS 368 | ruby 369 | 370 | DEPENDENCIES 371 | awesome_print 372 | better_errors 373 | binding_of_caller 374 | bootstrap-sass (~> 3.3.6) 375 | byebug 376 | capybara 377 | coffee-rails (~> 4.1.0) 378 | country_select (~> 2.5) 379 | devise (~> 3.5) 380 | email_spec 381 | guard 382 | guard-bundler 383 | guard-livereload 384 | guard-rails 385 | guard-rspec 386 | hirb 387 | jbuilder (~> 2.0) 388 | jquery-rails 389 | json (~> 1.8) 390 | letter_opener 391 | paloma (~> 5.0) 392 | pry 393 | pry-byebug (= 3.2.0) 394 | pry-doc 395 | pry-rails 396 | pry-remote 397 | quiet_assets 398 | rails (= 4.2.6) 399 | reform (~> 2.1) 400 | responders (~> 2.1) 401 | rspec-rails 402 | sass-rails (~> 5.0) 403 | sdoc (~> 0.4.0) 404 | shoulda-matchers 405 | sidekiq (~> 4.1) 406 | simple_form (~> 3.2) 407 | softlayer (~> 0.0) 408 | spring 409 | spring-commands-rspec 410 | sqlite3 411 | thin 412 | turbolinks (= 5.0.0.beta2) 413 | turnip 414 | uglifier (>= 1.3.0) 415 | web-console (~> 2.0) 416 | 417 | BUNDLED WITH 418 | 1.12.2 419 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | ## Uncomment and set this to only include directories you want to watch 5 | # directories %w(app lib config test spec feature) 6 | 7 | ## Uncomment to clear the screen before every task 8 | # clearing :on 9 | 10 | ## Guard internally checks for changes in the Guardfile and exits. 11 | ## If you want Guard to automatically start up again, run guard in a 12 | ## shell loop, e.g.: 13 | ## 14 | ## $ while bundle exec guard; do echo "Restarting Guard..."; done 15 | ## 16 | ## Note: if you are using the `directories` clause above and you are not 17 | ## watching the project directory ('.'), the you will want to move the Guardfile 18 | ## to a watched dir and symlink it back, e.g. 19 | # 20 | # $ mkdir config 21 | # $ mv Guardfile config/ 22 | # $ ln -s config/Guardfile . 23 | # 24 | # and, you'll have to watch "config/Guardfile" instead of "Guardfile" 25 | 26 | # Workaround to make guard bundler works with bundler >= v1.12.0 27 | # more info https://github.com/guard/guard-bundler/issues/32 28 | Open3.popen3("gem contents bundler") do |i, o| 29 | Kernel.system("gem install bundler") if o.read.empty? 30 | end 31 | 32 | guard :bundler do 33 | require 'guard/bundler' 34 | require 'guard/bundler/verify' 35 | helper = Guard::Bundler::Verify.new 36 | 37 | files = ['Gemfile'] 38 | files += Dir['*.gemspec'] if files.any? { |f| helper.uses_gemspec?(f) } 39 | 40 | # Assume files are symlinked from somewhere 41 | files.each { |file| watch(helper.real_path(file)) } 42 | end 43 | 44 | guard 'livereload' do 45 | watch(%r{app/views/.+\.(erb|haml|slim)$}) 46 | watch(%r{app/helpers/.+\.rb}) 47 | watch(%r{public/.+\.(css|js|html)}) 48 | watch(%r{config/locales/.+\.yml}) 49 | # Rails Assets Pipeline 50 | watch(%r{(app|vendor)(/assets/\w+/(.+\.(css|js|html|png|jpg))).*}) { |m| "/assets/#{m[3]}" } 51 | end 52 | 53 | guard 'rails' do 54 | watch('Gemfile.lock') 55 | watch(%r{^(config|lib)/.*}) 56 | ignore(%r{config/routes.rb}) 57 | end 58 | 59 | 60 | # Note: The cmd option is now required due to the increasing number of ways 61 | # rspec may be run, below are examples of the most common uses. 62 | # * bundler: 'bundle exec rspec' 63 | # * bundler binstubs: 'bin/rspec' 64 | # * spring: 'bin/rspec' (This will use spring if running and you have 65 | # installed the spring binstubs per the docs) 66 | # * zeus: 'zeus rspec' (requires the server to be started separately) 67 | # * 'just' rspec: 'rspec' 68 | 69 | guard :rspec, cmd: "bundle exec rspec" do 70 | require "guard/rspec/dsl" 71 | dsl = Guard::RSpec::Dsl.new(self) 72 | 73 | # Feel free to open issues for suggestions and improvements 74 | 75 | # RSpec files 76 | rspec = dsl.rspec 77 | watch(rspec.spec_helper) { rspec.spec_dir } 78 | watch(rspec.spec_support) { rspec.spec_dir } 79 | watch(rspec.spec_files) 80 | 81 | # Ruby files 82 | ruby = dsl.ruby 83 | dsl.watch_spec_files_for(ruby.lib_files) 84 | 85 | # Rails files 86 | rails = dsl.rails(view_extensions: %w(erb haml slim)) 87 | dsl.watch_spec_files_for(rails.app_files) 88 | dsl.watch_spec_files_for(rails.views) 89 | 90 | watch(rails.controllers) do |m| 91 | [ 92 | rspec.spec.("routing/#{m[1]}_routing"), 93 | rspec.spec.("controllers/#{m[1]}_controller"), 94 | rspec.spec.("acceptance/#{m[1]}") 95 | ] 96 | end 97 | 98 | # Rails config changes 99 | watch(rails.spec_helper) { rspec.spec_dir } 100 | watch(rails.routes) { "#{rspec.spec_dir}/routing" } 101 | watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" } 102 | 103 | # Capybara features specs 104 | watch(rails.view_dirs) { |m| rspec.spec.("features/#{m[1]}") } 105 | 106 | # Turnip features and steps 107 | watch(%r{^spec/acceptance/(.+)\.feature$}) 108 | watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m| 109 | Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance" 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 IBM Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stratos 2 | 3 | Would like to see how SoftLayer API can help on your cloud automating tasks? Are you ready for a demonstration? Fasten your seat belts! 4 | 5 | This project is a Ruby on Rails application that offers the possibility to interact to SoftLayer API, exposing a web interface and showing some tasks using your API key, and you give the rules. 6 | 7 | ## Installation 8 | 9 | To run on your machine you need to follow the steps below: 10 | 11 | * bundle install (to install Ruby dependencies) 12 | * no need to configure database (we are not persisting any data) 13 | * rails s 14 | * Access http://localhost:3000/ 15 | * Login with your API Key 16 | 17 | If you'd like to test in a production environment, it's normal rails deployment, set `SECRET_KEY_BASE` variable and run `rake assets:precompile`, that's it! 18 | 19 | ## Security 20 | 21 | Don't worry about logging with your API user and key, because Stratos was develop to avoid data leakage, so we assume some premisses: 22 | 23 | * We store your API user and key as a cookie on your browser (vanished when you close your browser) 24 | * Cookies are signed with rails app key and stored in _encrypted_ form, check this [page](http://api.rubyonrails.org/classes/ActionDispatch/Cookies.html) for more info on cookies 25 | * API Key won't be saved on log, as you log, Rails will replace and show `"api_key"=>"[FILTERED]"` on log 26 | * Disabled logging on Jobs that receive api user / key to process API info on background. 27 | * We connect to SoftLayer API using HTTPS 28 | 29 | ## Roadmap 30 | 31 | 1. Support creation of Cloud Servers 32 | 2. Support creation of Hourly Bare Metal 33 | 3. DNS Support 34 | 4. Support creation of more complex Bare Metal packages 35 | 5. Tickets Interaction 36 | 6. Release 0.1 37 | 38 | ## Technical Premisses 39 | 40 | * (Unofficial) SoftLayer Client 41 | 42 | [SoftLayer](http://github.com/zertico/softlayer) is a (unofficial) ruby library to talk to SLAPI, it has high level Ruby models, and its the core of this application, if you're a ruby programmer and want to automate some SoftLayer task, we do **really** recommends you to check it! 43 | 44 | * Twitter bootstrap 45 | 46 | Using twitter bootstrap in the simplest possible way, so it makes easir for you to customize the views. 47 | 48 | * Trailblazer 49 | 50 | As its own description _Trailblazer is a thin layer on top of Rails. It gently enforces encapsulation, an intuitive code structure and gives you an object-oriented architecture._, so it makes easier to create the integration layer between Rails and SoftLayer API once we don't use ActiveRecord/ActiveModel. 51 | 52 | ## Contributing 53 | 54 | TODO: Write CONTRIBUTING file 55 | 56 | ## Information 57 | 58 | You can get in touch in #softlayer at freenode. 59 | 60 | TODO: Should create a google groups or mailing for questions and leave GH just for bugs? 61 | 62 | ### Questions and Bug reports 63 | 64 | If you have any question, please open an issue! If you discover any bugs, feel free to create an issue on GitHub. Please add as much information as possible to help us fixing the possible bug. We also encourage you to help even more by forking and sending us a pull request. 65 | 66 | https://github.com/softlayer/stratos/issues 67 | 68 | ## Maintainers 69 | 70 | * Celso Fernandes (https://github.com/fernandes) 71 | 72 | ## License 73 | 74 | [MIT License](LICENSE.md). Copyright (c) 2015 IBM Corporation. 75 | -------------------------------------------------------------------------------- /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 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softlayer/stratos/3c2c8aecba8e625538ff912fac15b8036669d662/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 any plugin's vendor/assets/javascripts directory 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 turbolinks 16 | //= require paloma 17 | //= require bootstrap-sprockets 18 | //= require_tree . 19 | 20 | $(document).on('click', '.panel-heading span.clickable', function(e){ 21 | var $this = $(this); 22 | if(!$this.hasClass('panel-collapsed')) { 23 | $this.parents('.panel').find('.panel-body').slideUp(); 24 | $this.addClass('panel-collapsed'); 25 | $this.find('i').removeClass('glyphicon-chevron-up').addClass('glyphicon-chevron-down'); 26 | } else { 27 | $this.parents('.panel').find('.panel-body').slideDown(); 28 | $this.removeClass('panel-collapsed'); 29 | $this.find('i').removeClass('glyphicon-chevron-down').addClass('glyphicon-chevron-up'); 30 | } 31 | }) 32 | 33 | var initializePaloma = function() { 34 | Paloma.start(); 35 | } 36 | 37 | $(document).on('turbolinks:load', function(){ 38 | initializePaloma(); 39 | }); 40 | 41 | $(document).on('ajax:send', function(event, xhr){ 42 | var link = $(event.target); 43 | var message = link.data('loading-message'); 44 | var progressType = link.data('loading-progress-type'); 45 | if (message === undefined) { message = "Loading" }; 46 | if (progressType === undefined) { progressType = "success" }; 47 | waitingDialog.show(message, { progressType: progressType }); 48 | }); 49 | 50 | $(document).on('ajax:complete', function(event, xhr, status){ 51 | waitingDialog.hide(); 52 | }); 53 | 54 | 55 | document.addEventListener("turbolinks:click", function(event) { 56 | var $target = $(event.target); 57 | var loading_message = $target.data('loading-message'); 58 | if (loading_message !== undefined) { 59 | var progressType = $target.data('loading-progress-type'); 60 | if (progressType === undefined) { progressType = "success" }; 61 | waitingDialog.show(loading_message, { progressType: progressType }); 62 | } 63 | }) 64 | 65 | document.addEventListener("turbolinks:before-cache", function(event) { 66 | waitingDialog.hide(); 67 | }) 68 | -------------------------------------------------------------------------------- /app/assets/javascripts/support/waiting_dialog.js: -------------------------------------------------------------------------------- 1 | var waitingDialog = waitingDialog || (function ($) { 2 | 'use strict'; 3 | 4 | // Creating modal dialog's DOM 5 | var $dialog = $( 6 | '
'); 14 | 15 | return { 16 | /** 17 | * Opens our dialog 18 | * @param message Custom message 19 | * @param options Custom options: 20 | * options.dialogSize - bootstrap postfix for dialog size, e.g. "sm", "m"; 21 | * options.progressType - bootstrap postfix for progress bar type, e.g. "success", "warning". 22 | */ 23 | show: function (message, options) { 24 | // Assigning defaults 25 | if (typeof options === 'undefined') { 26 | options = {}; 27 | } 28 | if (typeof message === 'undefined') { 29 | message = 'Loading'; 30 | } 31 | var settings = $.extend({ 32 | dialogSize: 'm', 33 | progressType: '', 34 | onHide: null // This callback runs after the dialog was hidden 35 | }, options); 36 | 37 | // Configuring dialog 38 | $dialog.find('.modal-dialog').attr('class', 'modal-dialog').addClass('modal-' + settings.dialogSize); 39 | $dialog.find('.progress-bar').attr('class', 'progress-bar'); 40 | if (settings.progressType) { 41 | $dialog.find('.progress-bar').addClass('progress-bar-' + settings.progressType); 42 | } 43 | $dialog.find('h3').text(message); 44 | // Adding callbacks 45 | if (typeof settings.onHide === 'function') { 46 | $dialog.off('hidden.bs.modal').on('hidden.bs.modal', function (e) { 47 | settings.onHide.call($dialog); 48 | }); 49 | } 50 | // Opening dialog 51 | $dialog.modal(); 52 | }, 53 | /** 54 | * Closes dialog 55 | */ 56 | hide: function () { 57 | $dialog.modal('hide'); 58 | } 59 | }; 60 | 61 | })(jQuery); 62 | -------------------------------------------------------------------------------- /app/assets/javascripts/virtual_machines.js: -------------------------------------------------------------------------------- 1 | var VirtualMachinesController = Paloma.controller('VirtualMachines'); 2 | 3 | VirtualMachinesController.prototype.new = function(){ 4 | var paloma = this; 5 | paloma.formPosting = false; 6 | 7 | $('#virtual_machine_hourly').change(function() { 8 | if ($(this).is(':checked')) { 9 | // show only hourly items 10 | $('#virtual_machine_bandwidth').val(1800); 11 | $('select option[data-recurringfee]').addClass('hidden'); 12 | $('select option[data-hourlyfee]').removeClass('hidden'); 13 | } else { 14 | // show only monthly items 15 | $('#virtual_machine_bandwidth').val(50367); 16 | $('select option[data-hourlyfee]').addClass('hidden'); 17 | $('select option[data-recurringfee]').removeClass('hidden'); 18 | } 19 | mountConflicts(this); 20 | chooseAlternativeOption(); 21 | refreshSelectItems(); 22 | refreshPriceBox(paloma); 23 | }); 24 | // hide options with location dependent true 25 | $("[data-location-dependent-flag=1]").addClass('hidden'); 26 | 27 | $("[data-resource]").change(function(){ 28 | // if we de-select datacenter option 29 | if($('[data-resource="datacenter"]').val() == ""){ 30 | // show all base prices 31 | hideDependant(); 32 | } else { 33 | // here we have a data center to process 34 | // non dependent are data centers with base prices, they are not present 35 | // on conflict hash, so we need to chack this way 36 | var non_dependent_flag = $(this).find(':selected').data('standard-prices-flag'); 37 | if (non_dependent_flag) { 38 | hideDependant(); 39 | mountConflicts(this); 40 | } else { 41 | // otherwise we need to process the datacenter based on conflict hash 42 | resource = $(this).data('resource'); 43 | hideNonDependant(); 44 | mountConflicts(this); 45 | } 46 | } 47 | 48 | // after we change the datacenter, we choose the alternative options to replace 49 | // and refresh the conflicts for alternative options 50 | chooseAlternativeOption(); 51 | refreshSelectItems(); 52 | refreshPriceBox(paloma); 53 | }); 54 | 55 | // when every select (except for datacenter) is changed 56 | // we need to show the options and hide for that price 57 | $("select[data-resource!=datacenter]").change(function(){ 58 | selected = $(this).find("option:checked"); 59 | non_dependent_flag = selected.data('location-dependent-flag'); 60 | if (!non_dependent_flag) { 61 | hideDependant(); 62 | } else { 63 | hideNonDependant(); 64 | } 65 | // hide conflicts for each price 66 | hideConflicts(conflictHash.priceToPrice, selected.val()); 67 | chooseAlternativeOption(); 68 | refreshSelectItems(); 69 | refreshPriceBox(paloma); 70 | }); 71 | 72 | loadDefaultOptions(this); 73 | }; 74 | 75 | function scrollToPriceBoxAnchor() { 76 | $('#price_box .category-name a').click(function(e) { 77 | e.preventDefault(); 78 | anchorHref = this.getAttribute('href'); 79 | $('html, body').animate({ 80 | scrollTop: $(anchorHref).offset().top - 90 81 | }, 200); 82 | }); 83 | }; 84 | 85 | function refreshPriceBox(paloma) { 86 | // bind ajax to form 87 | if (paloma.formPosting === false) { 88 | paloma.formPosting = true; 89 | form = $('form#new_virtual_machine'); 90 | formData = form.serialize(); 91 | jQuery.ajax({ 92 | type: "POST", 93 | url: '/virtual_machines/price_box', 94 | data: formData, 95 | complete: function() { 96 | paloma.formPosting = false; 97 | scrollToPriceBoxAnchor(); 98 | } 99 | }); 100 | } 101 | } 102 | function chooseAlternativeOption() { 103 | // choose alternative options 104 | $('select:not([data-resource="datacenter"])').each(function() { 105 | option = $(this).find("option:checked"); 106 | desc = option.data('item-description'); 107 | if (desc !== undefined) { 108 | new_option = $(this).find('option[class!="hidden"][data-item-description="'+desc+'"]'); 109 | if (new_option.size() === 0) { 110 | // TODO: improve the error message 111 | alert('removing option ' + desc); 112 | $(this).val(''); 113 | } 114 | $(this).val(new_option.val()); 115 | } 116 | }); 117 | } 118 | 119 | function loadDefaultOptions(paloma) { 120 | // set default options after everything is loaded 121 | paloma.params.defaultOptions.forEach(function(id) { 122 | option = $('select option[value='+id+']') 123 | select = option.closest('select'); 124 | select.val(option.val()); 125 | // select.trigger('change'); 126 | }); 127 | $('select option[data-hourlyfee]').addClass('hidden'); 128 | $('select option[data-recurringfee]').removeClass('hidden'); 129 | $('#virtual_machine_hourly').trigger('change'); 130 | mountConflicts(this); 131 | chooseAlternativeOption(); 132 | refreshSelectItems(); 133 | refreshPriceBox(paloma); 134 | } 135 | function hideDependant() { 136 | $("[data-location-dependent-flag=0]").removeClass('hidden'); 137 | $("[data-location-dependent-flag=1]").addClass('hidden'); 138 | } 139 | 140 | function hideNonDependant() { 141 | $("[data-location-dependent-flag=1]").removeClass('hidden'); 142 | } 143 | 144 | function refreshSelectItems() { 145 | // hide conflicts for other items 146 | $('select[data-resource!=datacenter]').each(function() { 147 | if ($(this).val()) { 148 | hideConflicts(conflictHash.priceToPrice, $(this).val()); 149 | hideConflicts(conflictHash.priceToLocation, $(this).val()); 150 | } 151 | }); 152 | } 153 | 154 | function hideConflicts(conflictHash, value){ 155 | if(conflictHash.hasOwnProperty(value)){ 156 | conflictHash[value].forEach(function(id){ 157 | $('option[value="' + id + '"]').addClass('hidden'); 158 | }); 159 | } 160 | } 161 | 162 | function mountConflicts(select){ 163 | errors = []; 164 | resource = $(select).data('resource'); 165 | $("select[data-resource]").each(function() { 166 | resource = $(this).data('resource'); 167 | var datacenter_id = $(this).find(':selected').data('datacenter-id'); 168 | if (datacenter_id === undefined) { 169 | datacenter_id = 957095; 170 | } 171 | if (resource === "datacenter") { 172 | hideConflicts(conflictHash.locationToPrice, datacenter_id); 173 | } else { 174 | hideConflicts(conflictHash.priceToPrice, datacenter_id); 175 | } 176 | }); 177 | } 178 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 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 any plugin's vendor/assets/stylesheets directory 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 bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any styles 10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new 11 | * file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | 17 | /* margin for fixed navbar */ 18 | body { 19 | padding-top: 70px; 20 | padding-bottom: 100px; 21 | } 22 | 23 | ul.actions { 24 | list-style-type: none; 25 | padding-left: 0px; 26 | } 27 | 28 | .fixedElement { 29 | background-color: #c0c0c0; 30 | position:fixed; 31 | top:0; 32 | width:100%; 33 | z-index:100; 34 | } 35 | 36 | .panel-heading span { 37 | margin-top: -20px; 38 | font-size: 15px; 39 | } 40 | 41 | .summary-total-container { 42 | padding: 10px; 43 | border: 1px solid #d1d3d4; 44 | border-radius: 5px; 45 | background-color: #f1f2f2; 46 | margin: 10px 0; 47 | } 48 | 49 | p.total-price { 50 | font-size: 20px; 51 | font-style: normal; 52 | font-weight: bold; 53 | margin-bottom: 10px; 54 | height: 20px; 55 | } 56 | 57 | p.total-fees { 58 | font-size: 12px; 59 | } 60 | 61 | div.summary-item { 62 | padding: 5px 0; 63 | font-size: 12px; 64 | min-height: 20px; 65 | border-bottom: 1px solid #d1d3d4; 66 | } 67 | -------------------------------------------------------------------------------- /app/assets/stylesheets/boostrap_sass.scss: -------------------------------------------------------------------------------- 1 | @import "bootstrap-sprockets"; 2 | @import "bootstrap"; 3 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | require "application_responder" 2 | 3 | class ApplicationController < ActionController::Base 4 | self.responder = ApplicationResponder 5 | respond_to :html 6 | before_action :check_softlayer_login 7 | before_action :set_current_user 8 | rescue_from Softlayer::Errors::SoapError, with: :handle_softlayer_exception 9 | 10 | # Prevent CSRF attacks by raising an exception. 11 | # For APIs, you may want to use :null_session instead. 12 | protect_from_forgery with: :exception 13 | 14 | def connection 15 | SoftlayerConnection.new(cookies.signed[:api_user], cookies.signed[:api_key]) 16 | end 17 | 18 | def set_current_user 19 | @current_user = cookies.signed[:api_user] 20 | end 21 | 22 | private 23 | def check_softlayer_login 24 | if cookies.signed[:api_user].nil? and cookies.signed[:api_key].nil? 25 | redirect_to login_path 26 | end 27 | end 28 | 29 | def handle_softlayer_exception(exception) 30 | return exception_invalid_login if exception.message.match /Invalid API token./ 31 | end 32 | 33 | def exception_invalid_login 34 | cookies.signed[:api_user] = nil 35 | cookies.signed[:api_key] = nil 36 | redirect_to login_path, alert: "API returned 'Invalid Credentials', Please Login Again" 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softlayer/stratos/3c2c8aecba8e625538ff912fac15b8036669d662/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/login_controller.rb: -------------------------------------------------------------------------------- 1 | class LoginController < ApplicationController 2 | skip_before_action :check_softlayer_login 3 | 4 | def new 5 | @form = LoginForm.new(OpenStruct.new) 6 | end 7 | 8 | def create 9 | @form = LoginForm.new(OpenStruct.new) 10 | if @form.validate(params[:login]) 11 | api_user = params[:login][:api_user] 12 | api_key = params[:login][:api_key] 13 | 14 | connection = SoftlayerConnection.new(api_user, api_key) 15 | if connection.valid? 16 | cookies.signed[:api_user] = api_user 17 | cookies.signed[:api_key] = api_key 18 | WarmCacheJob.logger = nil 19 | WarmCacheJob.perform_later(api_user, api_key) 20 | redirect_to root_path, notice: "Logged In Successfully" 21 | else 22 | redirect_to login_path, alert: "Invalid Credentials" 23 | end 24 | else 25 | redirect_to login_path, alert: "Invalid Credentials" 26 | end 27 | end 28 | 29 | def destroy 30 | cookies.signed[:api_user] = nil 31 | cookies.signed[:api_key] = nil 32 | redirect_to login_path, notice: 'Logged Out Successfully' 33 | end 34 | end -------------------------------------------------------------------------------- /app/controllers/virtual_machines_controller.rb: -------------------------------------------------------------------------------- 1 | class VirtualMachinesController < ApplicationController 2 | def index 3 | @virtual_machines = VirtualMachine.all 4 | end 5 | 6 | def new 7 | @vm = VirtualMachine.new 8 | @form = VirtualMachineForm.new(@vm) 9 | @conflict_hash = StoreHash.generate_hash 10 | @prices = @vm.components_price 11 | js defaultOptions: @vm.default_options 12 | end 13 | 14 | def create 15 | @vm = VirtualMachine.new 16 | @form = VirtualMachineForm.new(@vm) 17 | @conflict_hash = StoreHash.generate_hash 18 | @prices = @vm.components_price 19 | js '#new', defaultOptions: @vm.default_options 20 | if @form.validate(params[:virtual_machine]) 21 | @form.save do |hash| 22 | order_template = VirtualMachine.new(hash).template_hash 23 | container = Softlayer::Product::Order.verify_order(order_data: order_template) 24 | Softlayer::Product::Order.place_order(order_data: container) 25 | redirect_to root_path, notice: "Virtual Machine Created Successfully" 26 | end 27 | else 28 | render :new 29 | end 30 | end 31 | 32 | def price_box 33 | @vm = VirtualMachine.new(params[:virtual_machine].to_hash) 34 | @prices = @vm.components_price 35 | respond_to do |format| 36 | format.js { render action: "price_box" } 37 | end 38 | end 39 | 40 | def reboot 41 | vm = VirtualMachine.find(params[:id]) 42 | vm.reboot 43 | 44 | respond_to do |format| 45 | format.js { render } 46 | end 47 | end 48 | 49 | def power_cycle 50 | vm = VirtualMachine.find(params[:id]) 51 | 52 | if vm.running? 53 | vm.power_off 54 | @notice = "Machine turned off" 55 | elsif 56 | vm.power_on 57 | @notice = "Machine turned on" 58 | end 59 | 60 | respond_to do |format| 61 | format.js { render } 62 | end 63 | end 64 | 65 | def destroy 66 | vm = VirtualMachine.find(params[:id]) 67 | vm.destroy 68 | 69 | respond_to do |format| 70 | format.js { render } 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /app/forms/login_form.rb: -------------------------------------------------------------------------------- 1 | class LoginForm < Reform::Form 2 | property :api_user 3 | property :api_key 4 | 5 | validates :api_user, presence: true 6 | validates :api_key, presence: true 7 | end -------------------------------------------------------------------------------- /app/forms/virtual_machine_form.rb: -------------------------------------------------------------------------------- 1 | class VirtualMachineForm < Reform::Form 2 | property :hostname 3 | property :domain 4 | property :hourly 5 | property :datacenter 6 | property :guest_core 7 | property :ram 8 | property :os 9 | property :guest_disk0 10 | property :guest_disk1 11 | property :guest_disk2 12 | property :guest_disk3 13 | property :guest_disk4 14 | property :bandwidth 15 | property :port_speed 16 | property :sec_ip_addresses 17 | property :pri_ipv6_addresses 18 | property :static_ipv6_addresses 19 | property :os_addon 20 | property :cdp_backup 21 | property :control_panel 22 | property :database 23 | property :firewall 24 | property :av_spyware_protection 25 | property :intrusion_protection 26 | property :monitoring_package 27 | property :evault 28 | property :monitoring 29 | property :response 30 | property :bc_insurance 31 | 32 | validates :hostname, presence: true 33 | validates :domain, presence: true 34 | validates :hourly, presence: true 35 | validates :datacenter, presence: true, numericality: { only_integer: true } 36 | validates :guest_core, presence: true, numericality: { only_integer: true } 37 | validates :ram, presence: true, numericality: { only_integer: true } 38 | validates :os, presence: true, numericality: { only_integer: true } 39 | validates :guest_disk0, presence: true, numericality: { only_integer: true } 40 | validates :bandwidth, presence: true, numericality: { only_integer: true } 41 | validates :port_speed, presence: true, numericality: { only_integer: true } 42 | validates :monitoring, presence: true, numericality: { only_integer: true } 43 | validates :response, presence: true, numericality: { only_integer: true } 44 | end 45 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | def bootstrap_class_for(flash_type) 3 | { success: "alert-success", error: "alert-danger", alert: "alert-warning", notice: "alert-info" }[flash_type.to_sym] || flash_type.to_s 4 | end 5 | 6 | def flash_messages(opts = {}) 7 | flash.each do |msg_type, message| 8 | concat(content_tag(:div, message, class: "alert #{bootstrap_class_for(msg_type)} fade in") do 9 | concat content_tag(:button, 'x', class: "close", data: { dismiss: 'alert' }) 10 | concat t(message) 11 | end) 12 | end 13 | nil 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/helpers/form_helper.rb: -------------------------------------------------------------------------------- 1 | module FormHelper 2 | def error_tag(model, field) 3 | errors = model.errors[field] 4 | if errors.any? 5 | render("shared/form_helper/error_tag", errors: errors) 6 | else 7 | nil 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/jobs/warm_cache_job.rb: -------------------------------------------------------------------------------- 1 | class WarmCacheJob < ActiveJob::Base 2 | queue_as :default 3 | 4 | def perform(api_user, api_key) 5 | SoftlayerConnection.new(api_user, api_key) 6 | WarmDatacentersCacheJob.perform_later(api_user, api_key) 7 | WarmCreateOptionsCacheJob.perform_later(api_user, api_key) 8 | WarmPackageCacheJob.perform_later(api_user, api_key) 9 | WarmConfigurationCacheJob.perform_later(api_user, api_key) 10 | WarmProductCategoriesCacheJob.perform_later(api_user, api_key) 11 | WarmStoreHashCacheJob.perform_later(api_user, api_key) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/jobs/warm_configuration_cache_job.rb: -------------------------------------------------------------------------------- 1 | class WarmConfigurationCacheJob < WarmCacheJob 2 | queue_as :default 3 | 4 | def perform(api_user, api_key) 5 | SoftlayerConnection.new(api_user, api_key) 6 | vm = VirtualMachine.new 7 | vm.send(:configuration) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/jobs/warm_create_options_cache_job.rb: -------------------------------------------------------------------------------- 1 | class WarmCreateOptionsCacheJob < WarmCacheJob 2 | queue_as :default 3 | 4 | def perform(api_user, api_key) 5 | SoftlayerConnection.new(api_user, api_key) 6 | vm = VirtualMachine.new 7 | vm.send(:create_options) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/jobs/warm_datacenters_cache_job.rb: -------------------------------------------------------------------------------- 1 | class WarmDatacentersCacheJob < WarmCacheJob 2 | queue_as :default 3 | 4 | def perform(api_user, api_key) 5 | SoftlayerConnection.new(api_user, api_key) 6 | vm = VirtualMachine.new 7 | vm.send(:datacenters) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/jobs/warm_package_cache_job.rb: -------------------------------------------------------------------------------- 1 | class WarmPackageCacheJob < WarmCacheJob 2 | queue_as :default 3 | 4 | def perform(api_user, api_key) 5 | SoftlayerConnection.new(api_user, api_key) 6 | vm = VirtualMachine.new 7 | vm.send(:package) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/jobs/warm_product_categories_cache_job.rb: -------------------------------------------------------------------------------- 1 | class WarmProductCategoriesCacheJob < WarmCacheJob 2 | queue_as :default 3 | 4 | def perform(api_user, api_key) 5 | SoftlayerConnection.new(api_user, api_key) 6 | vm = VirtualMachine.new 7 | vm.send(:product_categories) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/jobs/warm_store_hash_cache_job.rb: -------------------------------------------------------------------------------- 1 | class WarmStoreHashCacheJob < WarmCacheJob 2 | queue_as :default 3 | 4 | def perform(api_user, api_key) 5 | SoftlayerConnection.new(api_user, api_key) 6 | StoreHash.generate_hash 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softlayer/stratos/3c2c8aecba8e625538ff912fac15b8036669d662/app/mailers/.keep -------------------------------------------------------------------------------- /app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softlayer/stratos/3c2c8aecba8e625538ff912fac15b8036669d662/app/models/.keep -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softlayer/stratos/3c2c8aecba8e625538ff912fac15b8036669d662/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/softlayer_connection.rb: -------------------------------------------------------------------------------- 1 | class SoftlayerConnection 2 | def initialize(user, key) 3 | @api_user = user 4 | @api_key = key 5 | connection 6 | end 7 | 8 | def valid? 9 | Softlayer::Account.get_current_user 10 | true 11 | rescue Softlayer::Errors::SoapError => e 12 | false 13 | end 14 | 15 | def connection 16 | Softlayer.configure do |config| 17 | config.username = @api_user 18 | config.api_key = @api_key 19 | config.open_timeout = 30 # if you want specify timeout (default 5) 20 | config.read_timeout = 30 # if you want specify timeout (default 5) 21 | end 22 | end 23 | end -------------------------------------------------------------------------------- /app/models/store_hash.rb: -------------------------------------------------------------------------------- 1 | class StoreHash 2 | attr_reader :pricing, :region, :conflict_hash 3 | 4 | def self.generate_hash 5 | Rails.cache.fetch("softlayer/package-46-conflict-hash", expires_in: 12.hours) do 6 | sh = self.new 7 | sh.conflict_hash.camelize_keys.to_json 8 | end 9 | end 10 | 11 | def initialize 12 | @pricing = StoreHash::Pricing.new 13 | @region = StoreHash::Region.new 14 | @conflict_hash = { price_to_price: {}, location_to_price: {}, price_to_location: {} } 15 | generate_conflict_hash 16 | end 17 | 18 | private 19 | def generate_conflict_hash 20 | @items 21 | hash = {} 22 | region.datacenters_ids.each { |x| conflict_hash[:location_to_price][x] = [] } 23 | items.each do |item| 24 | process_item_conflicts(item) if item.conflict_count != "0" 25 | process_location_conflicts(item) if item.location_conflict_count != "0" 26 | process_price_location_conflicts(item) 27 | process_capacity_conflicts(item) 28 | end 29 | hash 30 | end 31 | 32 | def process_item_conflicts(item) 33 | prices_for_item = pricing.prices_for(item.id) 34 | item.conflicts.each do |conflict| 35 | price_conflicts = pricing.prices_for(conflict.resource_table_id) 36 | if price_conflicts 37 | prices_for_item.each do |item_price| 38 | conflict_hash[:price_to_price][item_price] = [] if conflict_hash[:price_to_price][item_price].nil? 39 | conflict_hash[:price_to_price][item_price].concat price_conflicts 40 | end 41 | end 42 | end 43 | end 44 | 45 | def process_location_conflicts(item) 46 | prices_for_item = pricing.prices_for(item.id) 47 | item.location_conflicts.each do |conflict| 48 | 49 | # add a conflict location removing price 50 | datacenter = @region.datacenter_with_id(conflict.resource_table_id) 51 | conflict_hash[:location_to_price][datacenter.id].concat prices_for_item 52 | 53 | # add a conflict price removing location 54 | prices_for_item.each do |price| 55 | conflict_hash[:price_to_location][price] = [] if conflict_hash[:price_to_location][price].nil? 56 | conflict_hash[:price_to_location][price] << datacenter.id 57 | end 58 | end 59 | end 60 | 61 | def process_price_location_conflicts(item) 62 | dcs_ids = region.datacenters_ids 63 | 64 | # handle non base prices 65 | non_base_prices = item.prices.select { |x| x.location_group_id != nil } 66 | default_price = item.prices.select { |x| x.location_group_id.nil? }.first 67 | 68 | non_base_prices.each do |price| 69 | dcs_conflicts = region.datacenters_conflicts_for price.location_group_id 70 | dcs_conflicts.each { |x| conflict_hash[:location_to_price][x] << price.id } 71 | dcs_conflicts.each do |x| 72 | # do not add for non dependant datacenters 73 | conflict_hash[:location_to_price][x] << default_price.id if region.standard?(x) 74 | end 75 | end 76 | end 77 | 78 | def process_capacity_conflicts(item) 79 | item.prices.each do |price| 80 | unless price.capacity_restriction_type.nil? 81 | capacity_restriction_type = price.capacity_restriction_type 82 | capacity_restriction_type = "(CORE|PRIVATE_CORE)" if price.capacity_restriction_type == "CORE" 83 | # get restricted based on unit 84 | restricted = items.select { |x| x.units.match capacity_restriction_type unless x.units.nil? } 85 | # filter based on provided 86 | items_to_remove = restricted.select do |x| 87 | x.capacity.to_i > price.capacity_restriction_maximum.to_i or 88 | x.capacity.to_i < price.capacity_restriction_minimum.to_i 89 | end 90 | 91 | prices_to_remove = [] 92 | items_to_remove.each do |item| 93 | prices_to_remove.concat item.prices.map { |x| x.id } 94 | end 95 | 96 | prices_to_remove.each do |priceconf| 97 | conflict_hash[:price_to_price][priceconf] = [] if conflict_hash[:price_to_price][priceconf].nil? 98 | conflict_hash[:price_to_price][priceconf] << price.id 99 | end 100 | end 101 | end 102 | end 103 | 104 | def items 105 | Rails.cache.fetch("softlayer/package-46-items", expires_in: 12.hours) do 106 | package = Softlayer::Product::Package.find(46) 107 | items_mask = 'mask[conflictCount,conflicts,globalCategoryConflictCount,globalCategoryConflicts,locationConflictCount,locationConflicts,prices[pricingLocationGroup]]' 108 | package.mask(items_mask).get_items 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /app/models/store_hash/pricing.rb: -------------------------------------------------------------------------------- 1 | class StoreHash::Pricing 2 | def initialize 3 | generate_prices_hash 4 | generate_prices_items_hash 5 | end 6 | 7 | def prices_for(item_id) 8 | @prices_hash[item_id] 9 | end 10 | 11 | def item_for(price_id) 12 | @prices_item_hash[price_id] 13 | end 14 | 15 | def items 16 | Rails.cache.fetch("softlayer/package-46-items", expires_in: 12.hours) do 17 | package = Softlayer::Product::Package.find(46) 18 | items_mask = 'mask[conflictCount,conflicts,globalCategoryConflictCount,globalCategoryConflicts,locationConflictCount,locationConflicts,prices[pricingLocationGroup]]' 19 | package.mask(items_mask).get_items 20 | end 21 | end 22 | 23 | private 24 | def generate_prices_hash 25 | @prices_hash = {} 26 | items.each do |item| 27 | @prices_hash[item.id] = [] 28 | item.prices.each { |price| @prices_hash[item.id] << price.id } 29 | end 30 | end 31 | 32 | def generate_prices_items_hash 33 | @prices_item_hash = {} 34 | @prices_hash.each_pair do |k, v| 35 | v.each do |price_id| 36 | if @prices_item_hash.has_key? price_id 37 | @prices_item_hash[price_id] << k 38 | else 39 | @prices_item_hash[price_id] = [k] 40 | end 41 | end 42 | end 43 | end 44 | end -------------------------------------------------------------------------------- /app/models/store_hash/region.rb: -------------------------------------------------------------------------------- 1 | class StoreHash::Region 2 | def initialize 3 | generate_dcs_hash 4 | generate_dcs_groups_hash 5 | end 6 | 7 | def standard?(datacenter_id) 8 | dc = datacenters.select { |x| x.id == datacenter_id }.first 9 | return false if dc.groups.select { |x| x.location_group_type_id == 82 && x.id = 1 }.empty? 10 | true 11 | end 12 | 13 | def datacenter_with_id(datacenter_id) 14 | datacenters.select { |x| x.id == datacenter_id }.first 15 | end 16 | 17 | def datacenters_for(group_id) 18 | @dcs_groups_hash[group_id] 19 | end 20 | 21 | def datacenters_conflicts_for(group_id) 22 | return @dcs_hash.keys if datacenters_for(group_id) == nil 23 | @dcs_hash.keys - datacenters_for(group_id) 24 | end 25 | 26 | def datacenters 27 | Rails.cache.fetch("softlayer/datacenters", expires_in: 12.hours) do 28 | Softlayer::Location::Datacenter.mask('mask[groups]').get_datacenters 29 | end 30 | end 31 | 32 | def datacenters_ids 33 | @dcs_hash.keys 34 | end 35 | 36 | private 37 | def generate_dcs_hash 38 | @dcs_hash = {} 39 | datacenters.each do |dc| 40 | @dcs_hash[dc.id] = dc.groups.map { |x| x.id } 41 | end 42 | end 43 | 44 | def generate_dcs_groups_hash 45 | @dcs_groups_hash = {} 46 | @dcs_hash.each_pair do |k, v| 47 | v.each do |group_id| 48 | if @dcs_groups_hash.has_key? group_id 49 | @dcs_groups_hash[group_id] << k 50 | else 51 | @dcs_groups_hash[group_id] = [k] 52 | end 53 | end 54 | end 55 | end 56 | end -------------------------------------------------------------------------------- /app/models/virtual_machine.rb: -------------------------------------------------------------------------------- 1 | class VirtualMachine 2 | include ActiveModel::Model 3 | attr_reader :id 4 | 5 | attr_accessor :hostname, :domain, :hourly, :datacenter 6 | 7 | # system configuration 8 | attr_accessor :guest_core, :ram, :os, :guest_disk0, :guest_disk1, :guest_disk2, :guest_disk3, :guest_disk4 9 | 10 | # network options 11 | attr_accessor :bandwidth, :port_speed, :sec_ip_addresses, :pri_ipv6_addresses, :static_ipv6_addresses 12 | 13 | # system addons 14 | attr_accessor :os_addon, :cdp_backup, :control_panel, :database, :firewall, :av_spyware_protection 15 | attr_accessor :intrusion_protection, :monitoring_package 16 | 17 | # storage addons 18 | attr_accessor :evault 19 | 20 | # service addons 21 | attr_accessor :monitoring, :response, :bc_insurance 22 | 23 | # default 24 | attr_accessor :vpn_management, :vulnerability_scanner, :pri_ip_addresses, :notification, :remote_management 25 | 26 | # other 27 | attr_accessor :new_customer_setup, :premium, :managed_resource, :storage_backend_upgrade, :evault_plugin, :web_analytics 28 | 29 | delegate :fully_qualified_domain_name, :power_on, :power_off, :primary_ip_address, to: :softlayer_object 30 | delegate :primary_backend_ip_address, :provision_date, to: :softlayer_object 31 | 32 | def self.all 33 | objects = Softlayer::Account.mask('mask[powerState,status,activeTransactionCount,activeTransactions]').get_virtual_guests 34 | objects.map { |softlayer_object| self.build_from_softlayer_object(softlayer_object) } 35 | end 36 | 37 | def self.find(id) 38 | vm = self.new 39 | vm.softlayer_object = Softlayer::Virtual::Guest.mask('mask[powerState,status,activeTransactionCount,activeTransactions]').find(id) 40 | vm 41 | end 42 | 43 | def self.warm_cache 44 | vm = self.new 45 | vm.send(:datacenters) 46 | vm.send(:create_options) 47 | vm.send(:package) 48 | vm.send(:configuration) 49 | vm.send(:product_categories) 50 | end 51 | 52 | def self.build_from_softlayer_object(softlayer_object) 53 | vm = self.new 54 | vm.instance_variable_set(:@softlayer_object, softlayer_object) 55 | vm.instance_variable_set(:@id, softlayer_object.id) 56 | vm 57 | end 58 | 59 | def categories 60 | product_categories.map { |x| x.category_code } 61 | end 62 | 63 | def groups_for(category) 64 | product_categories.select { |x| x.category_code == category }.first.groups.map { |x| x.title } 65 | end 66 | 67 | def options_for_datacenter 68 | datacenters.map { |x| [x.long_name, x.id, { 69 | 'data-standard-prices-flag': x.groups.select { |x| x.location_group_type_id == 82 && x.id = 1 }.empty? ? 1 : 0, 70 | 'data-datacenter-id': x.id 71 | }] } 72 | end 73 | 74 | def form_options_for(category) 75 | Rails.cache.fetch("softlayer/46-form-options-for-#{category}", expires_in: 12.hours) do 76 | array = [] 77 | return options_for(category, nil) if groups_for(category)[0] == nil 78 | product_categories.select { |x| x.category_code == category }.each do |category| 79 | category.groups.each do |group| 80 | options = group.prices.map { |x| [x.item.description, x.id, { 81 | 'data-location-dependent-flag': (x.location_group_id.nil? ? 0 : 1), 82 | 'data-item-id': x.item.id, 83 | 'data-recurringfee': (x.recurring_fee || nil), 84 | 'data-nonrecurringfee': "0", 85 | 'data-hourlyfee': (x.hourly_recurring_fee || nil), 86 | 'data-ispriceflag': "1", 87 | 'data-item-description': x.item.description 88 | }] } 89 | array << [group.title, options] 90 | end 91 | end 92 | array 93 | end 94 | end 95 | 96 | def default_options 97 | default_items = product_categories.map do |preset| 98 | config = preset.preset_configurations 99 | if config.is_a? Array 100 | config.first.price.id 101 | else 102 | config.price.id unless config.price.nil? 103 | end 104 | end 105 | default_items.compact!.concat [1640, 1644, 273] 106 | end 107 | 108 | def components_price 109 | pricing = StoreHash::Pricing.new 110 | prices = {} 111 | prices[:total] = {hourly_fee: BigDecimal.new('0.0'), monthly_fee: BigDecimal.new('0.0')} 112 | prices[:components] = {} 113 | # process the datacenter 114 | unless datacenter.blank? 115 | location = datacenters.select { |x| x.id == datacenter.to_i }.first 116 | prices[:datacenter] = { 117 | name: "Data Center", 118 | item: location.name.upcase 119 | } 120 | end 121 | 122 | # process each component 123 | components.each do |component| 124 | category = product_categories.select { |x| x.category_code == component.to_s }.first 125 | price_id = self.send(component).to_i 126 | item_id = pricing.item_for(price_id).try(:first) 127 | if item_id 128 | item = pricing.items.select { |x| x.id == item_id }.try(:first) 129 | price = item.prices.select { |x| x.id == price_id }.try(:first) 130 | prices[:components][component] = { 131 | name: category.name, 132 | item: item.description, 133 | hourly_fee: price.hourly_recurring_fee, 134 | monthly_fee: price.recurring_fee 135 | } 136 | prices[:total][:hourly_fee] += BigDecimal.new(price.hourly_recurring_fee.to_s) 137 | prices[:total][:monthly_fee] += BigDecimal.new(price.recurring_fee.to_s) 138 | end 139 | end 140 | puts prices.inspect 141 | prices 142 | end 143 | 144 | def template_hash 145 | template = {} 146 | template['location'] = datacenter 147 | template['packageId'] = 46 148 | template['quantity'] = 1 149 | template['useHourlyPricing'] = !!hourly 150 | template['virtualGuests'] = [{domain: domain, hostname: hostname}] 151 | template['prices'] = [] 152 | components.each do |component| 153 | price_id = self.send(component).to_i 154 | template['prices'] << {id: price_id} if price_id != 0 155 | end 156 | template['prices'].concat [{"id"=>21}, {"id"=>57}, {"id"=>420}, {"id"=>418}, {"id"=>905}] 157 | template 158 | end 159 | 160 | def softlayer_object=(object) 161 | @softlayer_object = object 162 | end 163 | 164 | def reboot 165 | softlayer_object.reboot_hard 166 | end 167 | 168 | def power_state 169 | softlayer_object.power_state.key_name 170 | end 171 | 172 | def status 173 | softlayer_object.status.key_name 174 | end 175 | 176 | def destroy 177 | softlayer_object.delete_object 178 | self.freeze 179 | end 180 | 181 | def running? 182 | return true if power_state == "RUNNING" 183 | false 184 | end 185 | 186 | def active? 187 | return false unless softlayer_object.active_transaction_count == "0" 188 | return false if provision_date.nil? 189 | return true if status == "ACTIVE" 190 | false 191 | end 192 | 193 | private 194 | def softlayer_object 195 | @softlayer_object 196 | end 197 | 198 | def options_for(category, group) 199 | category = product_categories.select { |x| x.category_code == category }.first 200 | group = category.groups.select { |x| x.title == group }.first 201 | group.prices.map { |x| [x.item.description, x.id, { 202 | 'data-location-dependent-flag': (x.location_group_id.nil? ? 0 : 1), 203 | 'data-item-id': x.item.id, 204 | 'data-recurringfee': x.recurring_fee, 205 | 'data-nonrecurringfee': "0", 206 | 'data-hourlyfee': (x.hourly_recurring_fee || 0), 207 | 'data-ispriceflag': "1", 208 | 'data-item-description': x.item.description 209 | }] } 210 | end 211 | 212 | def components 213 | [:guest_core, :ram, :os, :guest_disk0, :guest_disk1, :guest_disk2, :guest_disk3, :guest_disk4, :bandwidth, :port_speed, :sec_ip_addresses, :pri_ipv6_addresses, :static_ipv6_addresses, :os_addon, :cdp_backup, :control_panel, :database, :firewall, :av_spyware_protection, :intrusion_protection, :monitoring_package ,:evault, :monitoring, :response, :bc_insurance] 214 | end 215 | 216 | def datacenters 217 | Rails.cache.fetch("softlayer/datacenters", expires_in: 12.hours) do 218 | Softlayer::Location::Datacenter.mask('mask[groups]').get_datacenters 219 | end 220 | end 221 | 222 | def create_options 223 | Rails.cache.fetch("softlayer/create_options", expires_in: 12.hours) do 224 | Softlayer::Virtual::Guest.get_create_object_options 225 | end 226 | end 227 | 228 | def package 229 | Rails.cache.fetch("softlayer/46-product_package", expires_in: 12.hours) do 230 | Softlayer::Product::Package.find(46) 231 | end 232 | end 233 | 234 | def configuration 235 | Rails.cache.fetch("softlayer/46-product_configuration", expires_in: 12.hours) do 236 | object_mask = 'mask[itemCategory[categoryCode,name,orderOptions]]' 237 | package.mask(object_mask).get_configuration 238 | end 239 | end 240 | 241 | def product_categories 242 | Rails.cache.fetch("softlayer/46-product_categories", expires_in: 12.hours) do 243 | obj_mask = 'mask[orderOptions,packageConfigurations,presetConfigurations]' 244 | package.mask(obj_mask).get_categories 245 | end 246 | end 247 | 248 | def location_ids_for(datacenter) 249 | datacenters.select { |x| x.name == datacenter }.first.groups.map { |x| x.id } 250 | end 251 | end 252 | -------------------------------------------------------------------------------- /app/models/virtual_machine/pricing.rb: -------------------------------------------------------------------------------- 1 | class VirtualMachine::Pricing 2 | class << self 3 | def price_for(vm) 4 | end 5 | 6 | def groups_for_datacenter(datacenter) 7 | datacenter = location_groups.select { |x| x.name == datacenter }.first 8 | datacenter.groups.map { |x| x.id } 9 | end 10 | 11 | def location_groups 12 | Rails.cache.fetch("softlayer/location_groups", expires_in: 12.hours) do 13 | Softlayer::Location::Datacenter.mask(location_object_mask).get_datacenters 14 | end 15 | end 16 | 17 | def price_options 18 | end 19 | end 20 | end -------------------------------------------------------------------------------- /app/views/application/_navbar.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 31 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |<%= number_to_currency(@prices[:total][:monthly_fee]) %>/mo.
4 |<%= number_to_currency(@prices[:total][:hourly_fee]) %>/ho.
5 |+ $0.00 in one-time fees
6 |Name | 7 |Public IP | 8 |Private IP | 9 |Start Date | 10 |11 | |
---|
You may have mistyped the address or the page may have moved.
63 |If you are the application owner check the logs for more information.
65 |Maybe you tried to change something you didn't have access to.
63 |If you are the application owner check the logs for more information.
65 |If you are the application owner check the logs for more information.
64 |