├── .gitignore ├── CONTRIBUTING.md ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Procfile ├── README.md ├── Rakefile ├── app.rb ├── model.rb ├── public ├── css │ ├── bootstrap-switch.css │ ├── bootstrap-theme.css │ ├── bootstrap.css │ └── nightlies.css ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 └── js │ ├── bootstrap-switch.js │ ├── bootstrap.js │ ├── dashboard.js │ └── jquery.js ├── views ├── dashboard.erb ├── landing.erb └── layout.erb └── warden_travis └── strategy.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/config 2 | .env 3 | nightlies.db -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Dependencies 2 | 3 | - Ruby 2.3.x (2.4.0 currently fails a `json` gem issue) 4 | - Postgresql 9.x 5 | 6 | ## Env Vars 7 | 8 | - `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` 9 | - `DATABASE_URL=postgres://localhost` or whatever appropriate location your database is at. 10 | 11 | ## Get going 12 | 13 | 1. Install deps: `bundle install` 14 | 1. Init the db schema: `rake schema` 15 | 1. Run the app: `bundle exec ruby app.rb` 16 | 1. Load the website: http://localhost:4567 17 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | source 'https://rubygems.org' 18 | 19 | gem 'sinatra' 20 | gem 'thin' 21 | gem 'travis' 22 | gem 'octokit' 23 | gem 'warden' 24 | gem 'warden-github' 25 | gem 'sinatra_auth_github' 26 | gem 'sequel' 27 | gem 'rake' 28 | gem 'snitcher' 29 | gem 'librato-rack' 30 | 31 | group :production do 32 | gem 'pg' 33 | end 34 | 35 | group :development do 36 | gem 'sqlite3' 37 | end 38 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (4.2.1) 5 | i18n (~> 0.7) 6 | json (~> 1.7, >= 1.7.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | addressable (2.3.8) 11 | aggregate (0.2.2) 12 | backports (3.6.4) 13 | coderay (1.1.0) 14 | daemons (1.2.2) 15 | ethon (0.7.3) 16 | ffi (>= 1.3.0) 17 | eventmachine (1.0.7) 18 | faraday (0.9.1) 19 | multipart-post (>= 1.2, < 3) 20 | faraday_middleware (0.9.1) 21 | faraday (>= 0.7.4, < 0.10) 22 | ffi (1.9.8) 23 | gh (0.14.0) 24 | addressable 25 | backports 26 | faraday (~> 0.8) 27 | multi_json (~> 1.0) 28 | net-http-persistent (>= 2.7) 29 | net-http-pipeline 30 | highline (1.7.2) 31 | i18n (0.7.0) 32 | json (1.8.2) 33 | launchy (2.4.3) 34 | addressable (~> 2.3) 35 | librato-metrics (1.5.1) 36 | aggregate (~> 0.2.2) 37 | faraday (~> 0.7) 38 | multi_json 39 | librato-rack (0.4.5) 40 | librato-metrics (~> 1.1) 41 | method_source (0.8.2) 42 | minitest (5.6.1) 43 | multi_json (1.11.0) 44 | multipart-post (2.0.0) 45 | net-http-persistent (2.9.4) 46 | net-http-pipeline (1.0.1) 47 | octokit (3.8.0) 48 | sawyer (~> 0.6.0, >= 0.5.3) 49 | pg (0.18.2) 50 | pry (0.9.12.6) 51 | coderay (~> 1.0) 52 | method_source (~> 0.8) 53 | slop (~> 3.4) 54 | pusher-client (0.6.2) 55 | json 56 | websocket (~> 1.0) 57 | rack (1.6.1) 58 | rack-protection (1.5.3) 59 | rack 60 | rake (10.4.2) 61 | sawyer (0.6.0) 62 | addressable (~> 2.3.5) 63 | faraday (~> 0.8, < 0.10) 64 | sequel (4.22.0) 65 | sinatra (1.4.6) 66 | rack (~> 1.4) 67 | rack-protection (~> 1.4) 68 | tilt (>= 1.3, < 3) 69 | sinatra_auth_github (1.2.0) 70 | sinatra (~> 1.0) 71 | warden-github (~> 1.2.0) 72 | slop (3.6.0) 73 | snitcher (0.3.1) 74 | sqlite3 (1.3.10) 75 | thin (1.6.3) 76 | daemons (~> 1.0, >= 1.0.9) 77 | eventmachine (~> 1.0) 78 | rack (~> 1.0) 79 | thread_safe (0.3.5) 80 | tilt (2.0.1) 81 | travis (1.7.6) 82 | addressable (~> 2.3) 83 | backports 84 | faraday (~> 0.9) 85 | faraday_middleware (~> 0.9, >= 0.9.1) 86 | gh (~> 0.13) 87 | highline (~> 1.6) 88 | launchy (~> 2.1) 89 | pry (~> 0.9, < 0.10) 90 | pusher-client (~> 0.4) 91 | typhoeus (~> 0.6, >= 0.6.8) 92 | typhoeus (0.7.1) 93 | ethon (>= 0.7.1) 94 | tzinfo (1.2.2) 95 | thread_safe (~> 0.1) 96 | warden (1.2.3) 97 | rack (>= 1.0) 98 | warden-github (1.2.0) 99 | activesupport (> 3.0) 100 | octokit (> 2.1.0) 101 | warden (> 1.0) 102 | websocket (1.2.2) 103 | 104 | PLATFORMS 105 | ruby 106 | 107 | DEPENDENCIES 108 | librato-rack 109 | octokit 110 | pg 111 | rake 112 | sequel 113 | sinatra 114 | sinatra_auth_github 115 | snitcher 116 | sqlite3 117 | thin 118 | travis 119 | warden 120 | warden-github 121 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec ruby app.rb 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Nightli.es](https://nightli.es/) 2 | 3 | Nightli.es provides automatic nightly builds for [Travis CI](https://travis-ci.org/). 4 | 5 | ## Why have nightly builds? 6 | 7 | When developing a library, often some components like other libraries or the 8 | language interpreter will use the latest available version. This can lead to 9 | nasty situations when an upstream component releases a new version that breaks 10 | your tests, but you don't find out until your next git push. Nightli.es 11 | ensures each project builds at least once every 24 hours so these failures will 12 | be caught as soon as possible. 13 | 14 | ## Why does Nightli.es require so many GitHub permissions? 15 | 16 | Nightli.es uses the Travis API to get the list of projects you have access to 17 | and to kick off builds. Unfortunately Travis' GitHub authentication integration 18 | requires that I request all the same permissions as Travis itself. Your GitHub 19 | OAuth tokens are never stored server-side, only your Travis API token is stored 20 | for use in starting builds via the API. 21 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'benchmark' 18 | 19 | require 'snitcher' 20 | 21 | require_relative './model' 22 | 23 | task :schema do 24 | Nightlies::Model.schema! 25 | end 26 | 27 | task :run do 28 | time = Benchmark.realtime { Nightlies::Model.run! } 29 | msg = "Finished in #{time} seconds." 30 | puts(msg) 31 | Snitcher.snitch(ENV['SNITCH_ID'], message: msg) if ENV['SNITCH_ID'] 32 | end 33 | -------------------------------------------------------------------------------- /app.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'sinatra' 18 | require 'sinatra_auth_github' 19 | require 'tilt/erb' 20 | require 'librato-rack' 21 | 22 | require_relative './model' 23 | require_relative './warden_travis/strategy' 24 | 25 | module Nightlies 26 | class Application < Sinatra::Application 27 | configure do 28 | # Let's get this party started! 29 | enable :sessions 30 | set :session_secret, ENV['GITHUB_VERIFIER_SECRET'] 31 | set :public_folder, 'public' 32 | use Rack::Protection::AuthenticityToken 33 | 34 | # Load librato 35 | use Librato::Rack if ENV['LIBRATO_TOKEN'] 36 | 37 | # Load the GithHub authentication stuffs. 38 | set :github_options, {scopes: 'read:org user:email repo:status write:repo_hook repo_deployment'} 39 | register Sinatra::Auth::Github 40 | 41 | # Reconfigure Warden to use our strategy instead. 42 | use Class.new { 43 | def initialize(app) 44 | @app = app 45 | end 46 | def call(env) 47 | env['warden'].config.default_strategies :travis 48 | @app.call(env) 49 | end 50 | } 51 | end 52 | 53 | helpers do 54 | def travis_token 55 | authenticated? && github_user.attribs['travis_token'] 56 | end 57 | 58 | def travis_api 59 | raise "No travis token" unless travis_token 60 | Travis::Client.new(access_token: travis_token, agent_info: 'nightli.es') 61 | end 62 | 63 | def no_cache! 64 | cache_control :no_cache, :no_store, :must_revalidate 65 | headers['Expires'] = '0' 66 | headers['Pragma'] = 'no-cache' 67 | end 68 | end 69 | 70 | before do 71 | headers 'Content-Type' => 'text/html; charset=utf8' 72 | end 73 | 74 | get '/' do 75 | no_cache! 76 | if !authenticated? 77 | erb :landing 78 | else 79 | @repos = travis_api.get_raw('/hooks')['hooks'].inject([]) do |memo, data| 80 | if data['admin'] && data['active'] 81 | row = Nightlies::Model.by_id(data['id']) 82 | memo << data.merge( 83 | 'last_nightly' => row && row[:last_nightly], 84 | 'enabled' => row && !!row[:travis_token], 85 | ) 86 | end 87 | memo 88 | end 89 | @extra_js = 'dashboard.js' 90 | erb :dashboard 91 | end 92 | end 93 | 94 | get '/login' do 95 | no_cache! 96 | authenticate! 97 | redirect '/' 98 | end 99 | 100 | get '/logout' do 101 | no_cache! 102 | logout! 103 | redirect '/' 104 | end 105 | 106 | post '/enable/:id' do 107 | logger.warn "Enabling nightly builds for #{params['slug']} via #{github_user.login}" 108 | Nightlies::Model.enable!(github_user, params['id'].to_i, params['slug']) 109 | content_type :json 110 | {success: true}.to_json 111 | end 112 | 113 | post '/disable/:id' do 114 | logger.warn "Disabling nightly builds for #{params['slug']} via #{github_user.login}" 115 | Nightlies::Model.disable!(github_user, params['id'].to_i) 116 | content_type :json 117 | {success: true}.to_json 118 | end 119 | 120 | run! if app_file == $0 121 | end 122 | end 123 | -------------------------------------------------------------------------------- /model.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2015, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require 'sequel' 18 | require 'travis' 19 | 20 | 21 | module Nightlies 22 | class Model 23 | def self.db 24 | @db ||= Sequel.connect(ENV['DATABASE_URL']) 25 | end 26 | 27 | def self.schema! 28 | db.create_table(:nightlies) do 29 | primary_key :id 30 | String :username 31 | String :owner 32 | String :name 33 | String :travis_token 34 | Time :last_nightly # Because the API doesn't see API-initiated builds. 35 | unique [:owner, :name] 36 | end 37 | end 38 | 39 | def self.enable!(user, repo_id, slug) 40 | owner, name = slug.split('/', 2) 41 | values = {owner: owner, name: name, username: user.login, travis_token: user.attribs['travis_token']} 42 | if db[:nightlies].filter(id: repo_id).update(values) == 0 43 | db[:nightlies].insert(values.merge(id: repo_id)) 44 | end 45 | end 46 | 47 | def self.disable!(user, repo_id) 48 | db[:nightlies].filter(id: repo_id).update(travis_token: nil) 49 | end 50 | 51 | def self.by_id(repo_id) 52 | db[:nightlies].filter(id: repo_id).first 53 | end 54 | 55 | def self.run! 56 | failure = true 57 | db[:nightlies].each do |data| 58 | begin 59 | # Skip disabled repos. 60 | next unless data[:travis_token] 61 | slug = "#{data[:owner]}/#{data[:name]}" 62 | puts "[#{slug}] Checking." 63 | travis = Travis::Client.new(access_token: data[:travis_token]) 64 | repo = travis.repo(slug) 65 | # Could speed this up in the future. 66 | last_push_build = repo.builds(event_type: 'push').first 67 | last_api_build = repo.builds(event_type: 'api').first 68 | if (last_push_build && last_push_build.pending?) || (last_api_build && last_api_build.pending?) 69 | # Currently building, we're done here. 70 | puts "[#{slug}] Already building." 71 | next 72 | end 73 | build_times = [data[:last_nightly] || Time.at(0)] 74 | build_times << last_push_build.finished_at if last_push_build 75 | build_times << last_api_build.finished_at if last_api_build 76 | last_build_time = build_times.max 77 | # Check if it has been 23.5 hours since the last build. The 0.5 is so 78 | # that if the last build was kicked off by us slightly more than 24 79 | # hours ago, we don't drift back by an hour each day. 80 | if Time.now - last_build_time > 60*60*23.5 81 | puts "[#{slug}] Requesting a build, last build time #{last_build_time}." 82 | self.run_build!(travis, slug) 83 | db[:nightlies].filter(id: data[:id]).update(last_nightly: Time.now) 84 | end 85 | # If any succeed then we probably aren't globally down. 86 | failure = false 87 | rescue Exception => ex 88 | puts "[#{slug}] ERROR: #{ex}" 89 | end 90 | end 91 | raise "All builds failed" if failure 92 | end 93 | 94 | def self.run_build!(travis, slug) 95 | encoded_slug = slug.gsub(/\//, '%2F') 96 | url = "/repo/#{encoded_slug}/requests" 97 | body = { 98 | request: { 99 | branch: 'master', 100 | message: "Nightly build for #{slug} via nightli.es.", 101 | }, 102 | } 103 | headers = { 104 | 'Travis-API-Version' => 3, 105 | } 106 | travis.post_raw(url, body, headers) 107 | end 108 | 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /public/css/bootstrap-switch.css: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * bootstrap-switch - v3.3.2 3 | * http://www.bootstrap-switch.org 4 | * ======================================================================== 5 | * Copyright 2012-2013 Mattia Larentis 6 | * 7 | * ======================================================================== 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * ======================================================================== 20 | */ 21 | 22 | .bootstrap-switch { 23 | display: inline-block; 24 | direction: ltr; 25 | cursor: pointer; 26 | border-radius: 4px; 27 | border: 1px solid; 28 | border-color: #cccccc; 29 | position: relative; 30 | text-align: left; 31 | overflow: hidden; 32 | line-height: 8px; 33 | z-index: 0; 34 | -webkit-user-select: none; 35 | -moz-user-select: none; 36 | -ms-user-select: none; 37 | user-select: none; 38 | vertical-align: middle; 39 | -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; 40 | transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; 41 | } 42 | .bootstrap-switch .bootstrap-switch-container { 43 | display: inline-block; 44 | top: 0; 45 | border-radius: 4px; 46 | -webkit-transform: translate3d(0, 0, 0); 47 | transform: translate3d(0, 0, 0); 48 | } 49 | .bootstrap-switch .bootstrap-switch-handle-on, 50 | .bootstrap-switch .bootstrap-switch-handle-off, 51 | .bootstrap-switch .bootstrap-switch-label { 52 | -webkit-box-sizing: border-box; 53 | -moz-box-sizing: border-box; 54 | box-sizing: border-box; 55 | cursor: pointer; 56 | display: inline-block !important; 57 | height: 100%; 58 | padding: 6px 12px; 59 | font-size: 14px; 60 | line-height: 20px; 61 | } 62 | .bootstrap-switch .bootstrap-switch-handle-on, 63 | .bootstrap-switch .bootstrap-switch-handle-off { 64 | text-align: center; 65 | z-index: 1; 66 | } 67 | .bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary, 68 | .bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary { 69 | color: #fff; 70 | background: #428bca; 71 | } 72 | .bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info, 73 | .bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info { 74 | color: #fff; 75 | background: #5bc0de; 76 | } 77 | .bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success, 78 | .bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success { 79 | color: #fff; 80 | background: #5cb85c; 81 | } 82 | .bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning, 83 | .bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning { 84 | background: #f0ad4e; 85 | color: #fff; 86 | } 87 | .bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger, 88 | .bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger { 89 | color: #fff; 90 | background: #d9534f; 91 | } 92 | .bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default, 93 | .bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default { 94 | color: #000; 95 | background: #eeeeee; 96 | } 97 | .bootstrap-switch .bootstrap-switch-label { 98 | text-align: center; 99 | margin-top: -1px; 100 | margin-bottom: -1px; 101 | z-index: 100; 102 | color: #333333; 103 | background: #ffffff; 104 | } 105 | .bootstrap-switch .bootstrap-switch-handle-on { 106 | border-bottom-left-radius: 3px; 107 | border-top-left-radius: 3px; 108 | } 109 | .bootstrap-switch .bootstrap-switch-handle-off { 110 | border-bottom-right-radius: 3px; 111 | border-top-right-radius: 3px; 112 | } 113 | .bootstrap-switch input[type='radio'], 114 | .bootstrap-switch input[type='checkbox'] { 115 | position: absolute !important; 116 | top: 0; 117 | left: 0; 118 | opacity: 0; 119 | filter: alpha(opacity=0); 120 | z-index: -1; 121 | } 122 | .bootstrap-switch input[type='radio'].form-control, 123 | .bootstrap-switch input[type='checkbox'].form-control { 124 | height: auto; 125 | } 126 | .bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-on, 127 | .bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-off, 128 | .bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-label { 129 | padding: 1px 5px; 130 | font-size: 12px; 131 | line-height: 1.5; 132 | } 133 | .bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-on, 134 | .bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-off, 135 | .bootstrap-switch.bootstrap-switch-small .bootstrap-switch-label { 136 | padding: 5px 10px; 137 | font-size: 12px; 138 | line-height: 1.5; 139 | } 140 | .bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-on, 141 | .bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-off, 142 | .bootstrap-switch.bootstrap-switch-large .bootstrap-switch-label { 143 | padding: 6px 16px; 144 | font-size: 18px; 145 | line-height: 1.33; 146 | } 147 | .bootstrap-switch.bootstrap-switch-disabled, 148 | .bootstrap-switch.bootstrap-switch-readonly, 149 | .bootstrap-switch.bootstrap-switch-indeterminate { 150 | cursor: default !important; 151 | } 152 | .bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-on, 153 | .bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-on, 154 | .bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-on, 155 | .bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-off, 156 | .bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-off, 157 | .bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-off, 158 | .bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-label, 159 | .bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-label, 160 | .bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-label { 161 | opacity: 0.5; 162 | filter: alpha(opacity=50); 163 | cursor: default !important; 164 | } 165 | .bootstrap-switch.bootstrap-switch-animate .bootstrap-switch-container { 166 | -webkit-transition: margin-left 0.5s; 167 | transition: margin-left 0.5s; 168 | } 169 | .bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-on { 170 | border-bottom-left-radius: 0; 171 | border-top-left-radius: 0; 172 | border-bottom-right-radius: 3px; 173 | border-top-right-radius: 3px; 174 | } 175 | .bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-off { 176 | border-bottom-right-radius: 0; 177 | border-top-right-radius: 0; 178 | border-bottom-left-radius: 3px; 179 | border-top-left-radius: 3px; 180 | } 181 | .bootstrap-switch.bootstrap-switch-focused { 182 | border-color: #66afe9; 183 | outline: 0; 184 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6); 185 | box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6); 186 | } 187 | .bootstrap-switch.bootstrap-switch-on .bootstrap-switch-label, 188 | .bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-off .bootstrap-switch-label { 189 | border-bottom-right-radius: 3px; 190 | border-top-right-radius: 3px; 191 | } 192 | .bootstrap-switch.bootstrap-switch-off .bootstrap-switch-label, 193 | .bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-on .bootstrap-switch-label { 194 | border-bottom-left-radius: 3px; 195 | border-top-left-radius: 3px; 196 | } 197 | -------------------------------------------------------------------------------- /public/css/bootstrap-theme.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.4 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | /*! 8 | * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=9b560b8cf2bcd3a66885) 9 | * Config saved to config.json and https://gist.github.com/9b560b8cf2bcd3a66885 10 | */ 11 | .btn-default, 12 | .btn-primary, 13 | .btn-success, 14 | .btn-info, 15 | .btn-warning, 16 | .btn-danger { 17 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); 18 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); 19 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); 20 | } 21 | .btn-default:active, 22 | .btn-primary:active, 23 | .btn-success:active, 24 | .btn-info:active, 25 | .btn-warning:active, 26 | .btn-danger:active, 27 | .btn-default.active, 28 | .btn-primary.active, 29 | .btn-success.active, 30 | .btn-info.active, 31 | .btn-warning.active, 32 | .btn-danger.active { 33 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 34 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 35 | } 36 | .btn-default .badge, 37 | .btn-primary .badge, 38 | .btn-success .badge, 39 | .btn-info .badge, 40 | .btn-warning .badge, 41 | .btn-danger .badge { 42 | text-shadow: none; 43 | } 44 | .btn:active, 45 | .btn.active { 46 | background-image: none; 47 | } 48 | .btn-default { 49 | background-image: -webkit-linear-gradient(top, #ffffff 0%, #e0e0e0 100%); 50 | background-image: -o-linear-gradient(top, #ffffff 0%, #e0e0e0 100%); 51 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ffffff), to(#e0e0e0)); 52 | background-image: linear-gradient(to bottom, #ffffff 0%, #e0e0e0 100%); 53 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); 54 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 55 | background-repeat: repeat-x; 56 | border-color: #dbdbdb; 57 | text-shadow: 0 1px 0 #fff; 58 | border-color: #ccc; 59 | } 60 | .btn-default:hover, 61 | .btn-default:focus { 62 | background-color: #e0e0e0; 63 | background-position: 0 -15px; 64 | } 65 | .btn-default:active, 66 | .btn-default.active { 67 | background-color: #e0e0e0; 68 | border-color: #dbdbdb; 69 | } 70 | .btn-default.disabled, 71 | .btn-default:disabled, 72 | .btn-default[disabled] { 73 | background-color: #e0e0e0; 74 | background-image: none; 75 | } 76 | .btn-primary { 77 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%); 78 | background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%); 79 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88)); 80 | background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%); 81 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0); 82 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 83 | background-repeat: repeat-x; 84 | border-color: #245580; 85 | } 86 | .btn-primary:hover, 87 | .btn-primary:focus { 88 | background-color: #265a88; 89 | background-position: 0 -15px; 90 | } 91 | .btn-primary:active, 92 | .btn-primary.active { 93 | background-color: #265a88; 94 | border-color: #245580; 95 | } 96 | .btn-primary.disabled, 97 | .btn-primary:disabled, 98 | .btn-primary[disabled] { 99 | background-color: #265a88; 100 | background-image: none; 101 | } 102 | .btn-success { 103 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); 104 | background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%); 105 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641)); 106 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); 107 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); 108 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 109 | background-repeat: repeat-x; 110 | border-color: #3e8f3e; 111 | } 112 | .btn-success:hover, 113 | .btn-success:focus { 114 | background-color: #419641; 115 | background-position: 0 -15px; 116 | } 117 | .btn-success:active, 118 | .btn-success.active { 119 | background-color: #419641; 120 | border-color: #3e8f3e; 121 | } 122 | .btn-success.disabled, 123 | .btn-success:disabled, 124 | .btn-success[disabled] { 125 | background-color: #419641; 126 | background-image: none; 127 | } 128 | .btn-info { 129 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 130 | background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 131 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2)); 132 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); 133 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); 134 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 135 | background-repeat: repeat-x; 136 | border-color: #28a4c9; 137 | } 138 | .btn-info:hover, 139 | .btn-info:focus { 140 | background-color: #2aabd2; 141 | background-position: 0 -15px; 142 | } 143 | .btn-info:active, 144 | .btn-info.active { 145 | background-color: #2aabd2; 146 | border-color: #28a4c9; 147 | } 148 | .btn-info.disabled, 149 | .btn-info:disabled, 150 | .btn-info[disabled] { 151 | background-color: #2aabd2; 152 | background-image: none; 153 | } 154 | .btn-warning { 155 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 156 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 157 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316)); 158 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); 159 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); 160 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 161 | background-repeat: repeat-x; 162 | border-color: #e38d13; 163 | } 164 | .btn-warning:hover, 165 | .btn-warning:focus { 166 | background-color: #eb9316; 167 | background-position: 0 -15px; 168 | } 169 | .btn-warning:active, 170 | .btn-warning.active { 171 | background-color: #eb9316; 172 | border-color: #e38d13; 173 | } 174 | .btn-warning.disabled, 175 | .btn-warning:disabled, 176 | .btn-warning[disabled] { 177 | background-color: #eb9316; 178 | background-image: none; 179 | } 180 | .btn-danger { 181 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 182 | background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 183 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a)); 184 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); 185 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); 186 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 187 | background-repeat: repeat-x; 188 | border-color: #b92c28; 189 | } 190 | .btn-danger:hover, 191 | .btn-danger:focus { 192 | background-color: #c12e2a; 193 | background-position: 0 -15px; 194 | } 195 | .btn-danger:active, 196 | .btn-danger.active { 197 | background-color: #c12e2a; 198 | border-color: #b92c28; 199 | } 200 | .btn-danger.disabled, 201 | .btn-danger:disabled, 202 | .btn-danger[disabled] { 203 | background-color: #c12e2a; 204 | background-image: none; 205 | } 206 | .thumbnail, 207 | .img-thumbnail { 208 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 209 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 210 | } 211 | .dropdown-menu > li > a:hover, 212 | .dropdown-menu > li > a:focus { 213 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 214 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 215 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 216 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 217 | background-repeat: repeat-x; 218 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 219 | background-color: #e8e8e8; 220 | } 221 | .dropdown-menu > .active > a, 222 | .dropdown-menu > .active > a:hover, 223 | .dropdown-menu > .active > a:focus { 224 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 225 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 226 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 227 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 228 | background-repeat: repeat-x; 229 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 230 | background-color: #2e6da4; 231 | } 232 | .navbar-default { 233 | background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%); 234 | background-image: -o-linear-gradient(top, #ffffff 0%, #f8f8f8 100%); 235 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ffffff), to(#f8f8f8)); 236 | background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%); 237 | background-repeat: repeat-x; 238 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 239 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 240 | border-radius: 4px; 241 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); 242 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); 243 | } 244 | .navbar-default .navbar-nav > .open > a, 245 | .navbar-default .navbar-nav > .active > a { 246 | background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); 247 | background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); 248 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2)); 249 | background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%); 250 | background-repeat: repeat-x; 251 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0); 252 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075); 253 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075); 254 | } 255 | .navbar-brand, 256 | .navbar-nav > li > a { 257 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25); 258 | } 259 | .navbar-inverse { 260 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222222 100%); 261 | background-image: -o-linear-gradient(top, #3c3c3c 0%, #222222 100%); 262 | background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222222)); 263 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%); 264 | background-repeat: repeat-x; 265 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 266 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 267 | } 268 | .navbar-inverse .navbar-nav > .open > a, 269 | .navbar-inverse .navbar-nav > .active > a { 270 | background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%); 271 | background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%); 272 | background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f)); 273 | background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%); 274 | background-repeat: repeat-x; 275 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0); 276 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25); 277 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25); 278 | } 279 | .navbar-inverse .navbar-brand, 280 | .navbar-inverse .navbar-nav > li > a { 281 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 282 | } 283 | .navbar-static-top, 284 | .navbar-fixed-top, 285 | .navbar-fixed-bottom { 286 | border-radius: 0; 287 | } 288 | @media (max-width: 767px) { 289 | .navbar .navbar-nav .open .dropdown-menu > .active > a, 290 | .navbar .navbar-nav .open .dropdown-menu > .active > a:hover, 291 | .navbar .navbar-nav .open .dropdown-menu > .active > a:focus { 292 | color: #fff; 293 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 294 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 295 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 296 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 297 | background-repeat: repeat-x; 298 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 299 | } 300 | } 301 | .alert { 302 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2); 303 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); 304 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); 305 | } 306 | .alert-success { 307 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 308 | background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 309 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc)); 310 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 311 | background-repeat: repeat-x; 312 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 313 | border-color: #b2dba1; 314 | } 315 | .alert-info { 316 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 317 | background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 318 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0)); 319 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 320 | background-repeat: repeat-x; 321 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 322 | border-color: #9acfea; 323 | } 324 | .alert-warning { 325 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 326 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 327 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0)); 328 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 329 | background-repeat: repeat-x; 330 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 331 | border-color: #f5e79e; 332 | } 333 | .alert-danger { 334 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 335 | background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 336 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3)); 337 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 338 | background-repeat: repeat-x; 339 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 340 | border-color: #dca7a7; 341 | } 342 | .progress { 343 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 344 | background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 345 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5)); 346 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 347 | background-repeat: repeat-x; 348 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 349 | } 350 | .progress-bar { 351 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%); 352 | background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%); 353 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090)); 354 | background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%); 355 | background-repeat: repeat-x; 356 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0); 357 | } 358 | .progress-bar-success { 359 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); 360 | background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%); 361 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44)); 362 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 363 | background-repeat: repeat-x; 364 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 365 | } 366 | .progress-bar-info { 367 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 368 | background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 369 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5)); 370 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 371 | background-repeat: repeat-x; 372 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 373 | } 374 | .progress-bar-warning { 375 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 376 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 377 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f)); 378 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 379 | background-repeat: repeat-x; 380 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 381 | } 382 | .progress-bar-danger { 383 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); 384 | background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%); 385 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c)); 386 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 387 | background-repeat: repeat-x; 388 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 389 | } 390 | .progress-bar-striped { 391 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); 392 | background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); 393 | background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); 394 | } 395 | .list-group { 396 | border-radius: 4px; 397 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 398 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 399 | } 400 | .list-group-item.active, 401 | .list-group-item.active:hover, 402 | .list-group-item.active:focus { 403 | text-shadow: 0 -1px 0 #286090; 404 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%); 405 | background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%); 406 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a)); 407 | background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%); 408 | background-repeat: repeat-x; 409 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0); 410 | border-color: #2b669a; 411 | } 412 | .list-group-item.active .badge, 413 | .list-group-item.active:hover .badge, 414 | .list-group-item.active:focus .badge { 415 | text-shadow: none; 416 | } 417 | .panel { 418 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 419 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 420 | } 421 | .panel-default > .panel-heading { 422 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 423 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 424 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 425 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 426 | background-repeat: repeat-x; 427 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 428 | } 429 | .panel-primary > .panel-heading { 430 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 431 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 432 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 433 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 434 | background-repeat: repeat-x; 435 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 436 | } 437 | .panel-success > .panel-heading { 438 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 439 | background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 440 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6)); 441 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 442 | background-repeat: repeat-x; 443 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 444 | } 445 | .panel-info > .panel-heading { 446 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 447 | background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 448 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3)); 449 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 450 | background-repeat: repeat-x; 451 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 452 | } 453 | .panel-warning > .panel-heading { 454 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 455 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 456 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc)); 457 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 458 | background-repeat: repeat-x; 459 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 460 | } 461 | .panel-danger > .panel-heading { 462 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 463 | background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 464 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc)); 465 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 466 | background-repeat: repeat-x; 467 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 468 | } 469 | .well { 470 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 471 | background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 472 | background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5)); 473 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 474 | background-repeat: repeat-x; 475 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 476 | border-color: #dcdcdc; 477 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); 478 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); 479 | } 480 | -------------------------------------------------------------------------------- /public/css/nightlies.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | 5 | body { 6 | position: relative; 7 | min-height: 100%; 8 | padding-top: 50px; 9 | padding-bottom: 60px; 10 | } 11 | 12 | p.cta { 13 | text-align: center 14 | } 15 | 16 | .dashboard-row.saving { 17 | position: relative; 18 | } 19 | 20 | .dashboard-row.saving::before { 21 | content: "⌛"; 22 | position: absolute; 23 | font-size: 30px; 24 | left: -25px; 25 | top: -4px; 26 | } 27 | 28 | .dashboard-slug, .dashboard-description { 29 | line-height: 30px; 30 | } 31 | 32 | .dashboard-slug { 33 | font-weight: bold; 34 | font-size: 130%; 35 | } 36 | 37 | .dashboard-description { 38 | font-style: italic; 39 | padding-left: 2px; 40 | } 41 | 42 | .footer { 43 | position: absolute; 44 | bottom: 0; 45 | width: 100%; 46 | height: 60px; 47 | line-height: 60px; 48 | color: #777; 49 | background-color: #f5f5f5; 50 | } 51 | -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderanger/nightlies/29bdfc76957b78e5539ea5a9d566ddcee556b15c/public/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderanger/nightlies/29bdfc76957b78e5539ea5a9d566ddcee556b15c/public/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderanger/nightlies/29bdfc76957b78e5539ea5a9d566ddcee556b15c/public/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderanger/nightlies/29bdfc76957b78e5539ea5a9d566ddcee556b15c/public/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /public/js/bootstrap-switch.js: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * bootstrap-switch - v3.3.2 3 | * http://www.bootstrap-switch.org 4 | * ======================================================================== 5 | * Copyright 2012-2013 Mattia Larentis 6 | * 7 | * ======================================================================== 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * ======================================================================== 20 | */ 21 | 22 | (function() { 23 | var __slice = [].slice; 24 | 25 | (function($, window) { 26 | "use strict"; 27 | var BootstrapSwitch; 28 | BootstrapSwitch = (function() { 29 | function BootstrapSwitch(element, options) { 30 | if (options == null) { 31 | options = {}; 32 | } 33 | this.$element = $(element); 34 | this.options = $.extend({}, $.fn.bootstrapSwitch.defaults, { 35 | state: this.$element.is(":checked"), 36 | size: this.$element.data("size"), 37 | animate: this.$element.data("animate"), 38 | disabled: this.$element.is(":disabled"), 39 | readonly: this.$element.is("[readonly]"), 40 | indeterminate: this.$element.data("indeterminate"), 41 | inverse: this.$element.data("inverse"), 42 | radioAllOff: this.$element.data("radio-all-off"), 43 | onColor: this.$element.data("on-color"), 44 | offColor: this.$element.data("off-color"), 45 | onText: this.$element.data("on-text"), 46 | offText: this.$element.data("off-text"), 47 | labelText: this.$element.data("label-text"), 48 | handleWidth: this.$element.data("handle-width"), 49 | labelWidth: this.$element.data("label-width"), 50 | baseClass: this.$element.data("base-class"), 51 | wrapperClass: this.$element.data("wrapper-class") 52 | }, options); 53 | this.$wrapper = $("
", { 54 | "class": (function(_this) { 55 | return function() { 56 | var classes; 57 | classes = ["" + _this.options.baseClass].concat(_this._getClasses(_this.options.wrapperClass)); 58 | classes.push(_this.options.state ? "" + _this.options.baseClass + "-on" : "" + _this.options.baseClass + "-off"); 59 | if (_this.options.size != null) { 60 | classes.push("" + _this.options.baseClass + "-" + _this.options.size); 61 | } 62 | if (_this.options.disabled) { 63 | classes.push("" + _this.options.baseClass + "-disabled"); 64 | } 65 | if (_this.options.readonly) { 66 | classes.push("" + _this.options.baseClass + "-readonly"); 67 | } 68 | if (_this.options.indeterminate) { 69 | classes.push("" + _this.options.baseClass + "-indeterminate"); 70 | } 71 | if (_this.options.inverse) { 72 | classes.push("" + _this.options.baseClass + "-inverse"); 73 | } 74 | if (_this.$element.attr("id")) { 75 | classes.push("" + _this.options.baseClass + "-id-" + (_this.$element.attr("id"))); 76 | } 77 | return classes.join(" "); 78 | }; 79 | })(this)() 80 | }); 81 | this.$container = $("
", { 82 | "class": "" + this.options.baseClass + "-container" 83 | }); 84 | this.$on = $("", { 85 | html: this.options.onText, 86 | "class": "" + this.options.baseClass + "-handle-on " + this.options.baseClass + "-" + this.options.onColor 87 | }); 88 | this.$off = $("", { 89 | html: this.options.offText, 90 | "class": "" + this.options.baseClass + "-handle-off " + this.options.baseClass + "-" + this.options.offColor 91 | }); 92 | this.$label = $("", { 93 | html: this.options.labelText, 94 | "class": "" + this.options.baseClass + "-label" 95 | }); 96 | this.$element.on("init.bootstrapSwitch", (function(_this) { 97 | return function() { 98 | return _this.options.onInit.apply(element, arguments); 99 | }; 100 | })(this)); 101 | this.$element.on("switchChange.bootstrapSwitch", (function(_this) { 102 | return function() { 103 | return _this.options.onSwitchChange.apply(element, arguments); 104 | }; 105 | })(this)); 106 | this.$container = this.$element.wrap(this.$container).parent(); 107 | this.$wrapper = this.$container.wrap(this.$wrapper).parent(); 108 | this.$element.before(this.options.inverse ? this.$off : this.$on).before(this.$label).before(this.options.inverse ? this.$on : this.$off); 109 | if (this.options.indeterminate) { 110 | this.$element.prop("indeterminate", true); 111 | } 112 | this._init(); 113 | this._elementHandlers(); 114 | this._handleHandlers(); 115 | this._labelHandlers(); 116 | this._formHandler(); 117 | this._externalLabelHandler(); 118 | this.$element.trigger("init.bootstrapSwitch"); 119 | } 120 | 121 | BootstrapSwitch.prototype._constructor = BootstrapSwitch; 122 | 123 | BootstrapSwitch.prototype.state = function(value, skip) { 124 | if (typeof value === "undefined") { 125 | return this.options.state; 126 | } 127 | if (this.options.disabled || this.options.readonly) { 128 | return this.$element; 129 | } 130 | if (this.options.state && !this.options.radioAllOff && this.$element.is(":radio")) { 131 | return this.$element; 132 | } 133 | if (this.options.indeterminate) { 134 | this.indeterminate(false); 135 | } 136 | value = !!value; 137 | this.$element.prop("checked", value).trigger("change.bootstrapSwitch", skip); 138 | return this.$element; 139 | }; 140 | 141 | BootstrapSwitch.prototype.toggleState = function(skip) { 142 | if (this.options.disabled || this.options.readonly) { 143 | return this.$element; 144 | } 145 | if (this.options.indeterminate) { 146 | this.indeterminate(false); 147 | return this.state(true); 148 | } else { 149 | return this.$element.prop("checked", !this.options.state).trigger("change.bootstrapSwitch", skip); 150 | } 151 | }; 152 | 153 | BootstrapSwitch.prototype.size = function(value) { 154 | if (typeof value === "undefined") { 155 | return this.options.size; 156 | } 157 | if (this.options.size != null) { 158 | this.$wrapper.removeClass("" + this.options.baseClass + "-" + this.options.size); 159 | } 160 | if (value) { 161 | this.$wrapper.addClass("" + this.options.baseClass + "-" + value); 162 | } 163 | this._width(); 164 | this._containerPosition(); 165 | this.options.size = value; 166 | return this.$element; 167 | }; 168 | 169 | BootstrapSwitch.prototype.animate = function(value) { 170 | if (typeof value === "undefined") { 171 | return this.options.animate; 172 | } 173 | value = !!value; 174 | if (value === this.options.animate) { 175 | return this.$element; 176 | } 177 | return this.toggleAnimate(); 178 | }; 179 | 180 | BootstrapSwitch.prototype.toggleAnimate = function() { 181 | this.options.animate = !this.options.animate; 182 | this.$wrapper.toggleClass("" + this.options.baseClass + "-animate"); 183 | return this.$element; 184 | }; 185 | 186 | BootstrapSwitch.prototype.disabled = function(value) { 187 | if (typeof value === "undefined") { 188 | return this.options.disabled; 189 | } 190 | value = !!value; 191 | if (value === this.options.disabled) { 192 | return this.$element; 193 | } 194 | return this.toggleDisabled(); 195 | }; 196 | 197 | BootstrapSwitch.prototype.toggleDisabled = function() { 198 | this.options.disabled = !this.options.disabled; 199 | this.$element.prop("disabled", this.options.disabled); 200 | this.$wrapper.toggleClass("" + this.options.baseClass + "-disabled"); 201 | return this.$element; 202 | }; 203 | 204 | BootstrapSwitch.prototype.readonly = function(value) { 205 | if (typeof value === "undefined") { 206 | return this.options.readonly; 207 | } 208 | value = !!value; 209 | if (value === this.options.readonly) { 210 | return this.$element; 211 | } 212 | return this.toggleReadonly(); 213 | }; 214 | 215 | BootstrapSwitch.prototype.toggleReadonly = function() { 216 | this.options.readonly = !this.options.readonly; 217 | this.$element.prop("readonly", this.options.readonly); 218 | this.$wrapper.toggleClass("" + this.options.baseClass + "-readonly"); 219 | return this.$element; 220 | }; 221 | 222 | BootstrapSwitch.prototype.indeterminate = function(value) { 223 | if (typeof value === "undefined") { 224 | return this.options.indeterminate; 225 | } 226 | value = !!value; 227 | if (value === this.options.indeterminate) { 228 | return this.$element; 229 | } 230 | return this.toggleIndeterminate(); 231 | }; 232 | 233 | BootstrapSwitch.prototype.toggleIndeterminate = function() { 234 | this.options.indeterminate = !this.options.indeterminate; 235 | this.$element.prop("indeterminate", this.options.indeterminate); 236 | this.$wrapper.toggleClass("" + this.options.baseClass + "-indeterminate"); 237 | this._containerPosition(); 238 | return this.$element; 239 | }; 240 | 241 | BootstrapSwitch.prototype.inverse = function(value) { 242 | if (typeof value === "undefined") { 243 | return this.options.inverse; 244 | } 245 | value = !!value; 246 | if (value === this.options.inverse) { 247 | return this.$element; 248 | } 249 | return this.toggleInverse(); 250 | }; 251 | 252 | BootstrapSwitch.prototype.toggleInverse = function() { 253 | var $off, $on; 254 | this.$wrapper.toggleClass("" + this.options.baseClass + "-inverse"); 255 | $on = this.$on.clone(true); 256 | $off = this.$off.clone(true); 257 | this.$on.replaceWith($off); 258 | this.$off.replaceWith($on); 259 | this.$on = $off; 260 | this.$off = $on; 261 | this.options.inverse = !this.options.inverse; 262 | return this.$element; 263 | }; 264 | 265 | BootstrapSwitch.prototype.onColor = function(value) { 266 | var color; 267 | color = this.options.onColor; 268 | if (typeof value === "undefined") { 269 | return color; 270 | } 271 | if (color != null) { 272 | this.$on.removeClass("" + this.options.baseClass + "-" + color); 273 | } 274 | this.$on.addClass("" + this.options.baseClass + "-" + value); 275 | this.options.onColor = value; 276 | return this.$element; 277 | }; 278 | 279 | BootstrapSwitch.prototype.offColor = function(value) { 280 | var color; 281 | color = this.options.offColor; 282 | if (typeof value === "undefined") { 283 | return color; 284 | } 285 | if (color != null) { 286 | this.$off.removeClass("" + this.options.baseClass + "-" + color); 287 | } 288 | this.$off.addClass("" + this.options.baseClass + "-" + value); 289 | this.options.offColor = value; 290 | return this.$element; 291 | }; 292 | 293 | BootstrapSwitch.prototype.onText = function(value) { 294 | if (typeof value === "undefined") { 295 | return this.options.onText; 296 | } 297 | this.$on.html(value); 298 | this._width(); 299 | this._containerPosition(); 300 | this.options.onText = value; 301 | return this.$element; 302 | }; 303 | 304 | BootstrapSwitch.prototype.offText = function(value) { 305 | if (typeof value === "undefined") { 306 | return this.options.offText; 307 | } 308 | this.$off.html(value); 309 | this._width(); 310 | this._containerPosition(); 311 | this.options.offText = value; 312 | return this.$element; 313 | }; 314 | 315 | BootstrapSwitch.prototype.labelText = function(value) { 316 | if (typeof value === "undefined") { 317 | return this.options.labelText; 318 | } 319 | this.$label.html(value); 320 | this._width(); 321 | this.options.labelText = value; 322 | return this.$element; 323 | }; 324 | 325 | BootstrapSwitch.prototype.handleWidth = function(value) { 326 | if (typeof value === "undefined") { 327 | return this.options.handleWidth; 328 | } 329 | this.options.handleWidth = value; 330 | this._width(); 331 | this._containerPosition(); 332 | return this.$element; 333 | }; 334 | 335 | BootstrapSwitch.prototype.labelWidth = function(value) { 336 | if (typeof value === "undefined") { 337 | return this.options.labelWidth; 338 | } 339 | this.options.labelWidth = value; 340 | this._width(); 341 | this._containerPosition(); 342 | return this.$element; 343 | }; 344 | 345 | BootstrapSwitch.prototype.baseClass = function(value) { 346 | return this.options.baseClass; 347 | }; 348 | 349 | BootstrapSwitch.prototype.wrapperClass = function(value) { 350 | if (typeof value === "undefined") { 351 | return this.options.wrapperClass; 352 | } 353 | if (!value) { 354 | value = $.fn.bootstrapSwitch.defaults.wrapperClass; 355 | } 356 | this.$wrapper.removeClass(this._getClasses(this.options.wrapperClass).join(" ")); 357 | this.$wrapper.addClass(this._getClasses(value).join(" ")); 358 | this.options.wrapperClass = value; 359 | return this.$element; 360 | }; 361 | 362 | BootstrapSwitch.prototype.radioAllOff = function(value) { 363 | if (typeof value === "undefined") { 364 | return this.options.radioAllOff; 365 | } 366 | value = !!value; 367 | if (value === this.options.radioAllOff) { 368 | return this.$element; 369 | } 370 | this.options.radioAllOff = value; 371 | return this.$element; 372 | }; 373 | 374 | BootstrapSwitch.prototype.onInit = function(value) { 375 | if (typeof value === "undefined") { 376 | return this.options.onInit; 377 | } 378 | if (!value) { 379 | value = $.fn.bootstrapSwitch.defaults.onInit; 380 | } 381 | this.options.onInit = value; 382 | return this.$element; 383 | }; 384 | 385 | BootstrapSwitch.prototype.onSwitchChange = function(value) { 386 | if (typeof value === "undefined") { 387 | return this.options.onSwitchChange; 388 | } 389 | if (!value) { 390 | value = $.fn.bootstrapSwitch.defaults.onSwitchChange; 391 | } 392 | this.options.onSwitchChange = value; 393 | return this.$element; 394 | }; 395 | 396 | BootstrapSwitch.prototype.destroy = function() { 397 | var $form; 398 | $form = this.$element.closest("form"); 399 | if ($form.length) { 400 | $form.off("reset.bootstrapSwitch").removeData("bootstrap-switch"); 401 | } 402 | this.$container.children().not(this.$element).remove(); 403 | this.$element.unwrap().unwrap().off(".bootstrapSwitch").removeData("bootstrap-switch"); 404 | return this.$element; 405 | }; 406 | 407 | BootstrapSwitch.prototype._width = function() { 408 | var $handles, handleWidth; 409 | $handles = this.$on.add(this.$off); 410 | $handles.add(this.$label).css("width", ""); 411 | handleWidth = this.options.handleWidth === "auto" ? Math.max(this.$on.width(), this.$off.width()) : this.options.handleWidth; 412 | $handles.width(handleWidth); 413 | this.$label.width((function(_this) { 414 | return function(index, width) { 415 | if (_this.options.labelWidth !== "auto") { 416 | return _this.options.labelWidth; 417 | } 418 | if (width < handleWidth) { 419 | return handleWidth; 420 | } else { 421 | return width; 422 | } 423 | }; 424 | })(this)); 425 | this._handleWidth = this.$on.outerWidth(); 426 | this._labelWidth = this.$label.outerWidth(); 427 | this.$container.width((this._handleWidth * 2) + this._labelWidth); 428 | return this.$wrapper.width(this._handleWidth + this._labelWidth); 429 | }; 430 | 431 | BootstrapSwitch.prototype._containerPosition = function(state, callback) { 432 | if (state == null) { 433 | state = this.options.state; 434 | } 435 | this.$container.css("margin-left", (function(_this) { 436 | return function() { 437 | var values; 438 | values = [0, "-" + _this._handleWidth + "px"]; 439 | if (_this.options.indeterminate) { 440 | return "-" + (_this._handleWidth / 2) + "px"; 441 | } 442 | if (state) { 443 | if (_this.options.inverse) { 444 | return values[1]; 445 | } else { 446 | return values[0]; 447 | } 448 | } else { 449 | if (_this.options.inverse) { 450 | return values[0]; 451 | } else { 452 | return values[1]; 453 | } 454 | } 455 | }; 456 | })(this)); 457 | if (!callback) { 458 | return; 459 | } 460 | return setTimeout(function() { 461 | return callback(); 462 | }, 50); 463 | }; 464 | 465 | BootstrapSwitch.prototype._init = function() { 466 | var init, initInterval; 467 | init = (function(_this) { 468 | return function() { 469 | _this._width(); 470 | return _this._containerPosition(null, function() { 471 | if (_this.options.animate) { 472 | return _this.$wrapper.addClass("" + _this.options.baseClass + "-animate"); 473 | } 474 | }); 475 | }; 476 | })(this); 477 | if (this.$wrapper.is(":visible")) { 478 | return init(); 479 | } 480 | return initInterval = window.setInterval((function(_this) { 481 | return function() { 482 | if (_this.$wrapper.is(":visible")) { 483 | init(); 484 | return window.clearInterval(initInterval); 485 | } 486 | }; 487 | })(this), 50); 488 | }; 489 | 490 | BootstrapSwitch.prototype._elementHandlers = function() { 491 | return this.$element.on({ 492 | "change.bootstrapSwitch": (function(_this) { 493 | return function(e, skip) { 494 | var state; 495 | e.preventDefault(); 496 | e.stopImmediatePropagation(); 497 | state = _this.$element.is(":checked"); 498 | _this._containerPosition(state); 499 | if (state === _this.options.state) { 500 | return; 501 | } 502 | _this.options.state = state; 503 | _this.$wrapper.toggleClass("" + _this.options.baseClass + "-off").toggleClass("" + _this.options.baseClass + "-on"); 504 | if (!skip) { 505 | if (_this.$element.is(":radio")) { 506 | $("[name='" + (_this.$element.attr('name')) + "']").not(_this.$element).prop("checked", false).trigger("change.bootstrapSwitch", true); 507 | } 508 | return _this.$element.trigger("switchChange.bootstrapSwitch", [state]); 509 | } 510 | }; 511 | })(this), 512 | "focus.bootstrapSwitch": (function(_this) { 513 | return function(e) { 514 | e.preventDefault(); 515 | return _this.$wrapper.addClass("" + _this.options.baseClass + "-focused"); 516 | }; 517 | })(this), 518 | "blur.bootstrapSwitch": (function(_this) { 519 | return function(e) { 520 | e.preventDefault(); 521 | return _this.$wrapper.removeClass("" + _this.options.baseClass + "-focused"); 522 | }; 523 | })(this), 524 | "keydown.bootstrapSwitch": (function(_this) { 525 | return function(e) { 526 | if (!e.which || _this.options.disabled || _this.options.readonly) { 527 | return; 528 | } 529 | switch (e.which) { 530 | case 37: 531 | e.preventDefault(); 532 | e.stopImmediatePropagation(); 533 | return _this.state(false); 534 | case 39: 535 | e.preventDefault(); 536 | e.stopImmediatePropagation(); 537 | return _this.state(true); 538 | } 539 | }; 540 | })(this) 541 | }); 542 | }; 543 | 544 | BootstrapSwitch.prototype._handleHandlers = function() { 545 | this.$on.on("click.bootstrapSwitch", (function(_this) { 546 | return function(event) { 547 | event.preventDefault(); 548 | event.stopPropagation(); 549 | _this.state(false); 550 | return _this.$element.trigger("focus.bootstrapSwitch"); 551 | }; 552 | })(this)); 553 | return this.$off.on("click.bootstrapSwitch", (function(_this) { 554 | return function(event) { 555 | event.preventDefault(); 556 | event.stopPropagation(); 557 | _this.state(true); 558 | return _this.$element.trigger("focus.bootstrapSwitch"); 559 | }; 560 | })(this)); 561 | }; 562 | 563 | BootstrapSwitch.prototype._labelHandlers = function() { 564 | return this.$label.on({ 565 | "mousedown.bootstrapSwitch touchstart.bootstrapSwitch": (function(_this) { 566 | return function(e) { 567 | if (_this._dragStart || _this.options.disabled || _this.options.readonly) { 568 | return; 569 | } 570 | e.preventDefault(); 571 | e.stopPropagation(); 572 | _this._dragStart = (e.pageX || e.originalEvent.touches[0].pageX) - parseInt(_this.$container.css("margin-left"), 10); 573 | if (_this.options.animate) { 574 | _this.$wrapper.removeClass("" + _this.options.baseClass + "-animate"); 575 | } 576 | return _this.$element.trigger("focus.bootstrapSwitch"); 577 | }; 578 | })(this), 579 | "mousemove.bootstrapSwitch touchmove.bootstrapSwitch": (function(_this) { 580 | return function(e) { 581 | var difference; 582 | if (_this._dragStart == null) { 583 | return; 584 | } 585 | e.preventDefault(); 586 | difference = (e.pageX || e.originalEvent.touches[0].pageX) - _this._dragStart; 587 | if (difference < -_this._handleWidth || difference > 0) { 588 | return; 589 | } 590 | _this._dragEnd = difference; 591 | return _this.$container.css("margin-left", "" + _this._dragEnd + "px"); 592 | }; 593 | })(this), 594 | "mouseup.bootstrapSwitch touchend.bootstrapSwitch": (function(_this) { 595 | return function(e) { 596 | var state; 597 | if (!_this._dragStart) { 598 | return; 599 | } 600 | e.preventDefault(); 601 | if (_this.options.animate) { 602 | _this.$wrapper.addClass("" + _this.options.baseClass + "-animate"); 603 | } 604 | if (_this._dragEnd) { 605 | state = _this._dragEnd > -(_this._handleWidth / 2); 606 | _this._dragEnd = false; 607 | _this.state(_this.options.inverse ? !state : state); 608 | } else { 609 | _this.state(!_this.options.state); 610 | } 611 | return _this._dragStart = false; 612 | }; 613 | })(this), 614 | "mouseleave.bootstrapSwitch": (function(_this) { 615 | return function(e) { 616 | return _this.$label.trigger("mouseup.bootstrapSwitch"); 617 | }; 618 | })(this) 619 | }); 620 | }; 621 | 622 | BootstrapSwitch.prototype._externalLabelHandler = function() { 623 | var $externalLabel; 624 | $externalLabel = this.$element.closest("label"); 625 | return $externalLabel.on("click", (function(_this) { 626 | return function(event) { 627 | event.preventDefault(); 628 | event.stopImmediatePropagation(); 629 | if (event.target === $externalLabel[0]) { 630 | return _this.toggleState(); 631 | } 632 | }; 633 | })(this)); 634 | }; 635 | 636 | BootstrapSwitch.prototype._formHandler = function() { 637 | var $form; 638 | $form = this.$element.closest("form"); 639 | if ($form.data("bootstrap-switch")) { 640 | return; 641 | } 642 | return $form.on("reset.bootstrapSwitch", function() { 643 | return window.setTimeout(function() { 644 | return $form.find("input").filter(function() { 645 | return $(this).data("bootstrap-switch"); 646 | }).each(function() { 647 | return $(this).bootstrapSwitch("state", this.checked); 648 | }); 649 | }, 1); 650 | }).data("bootstrap-switch", true); 651 | }; 652 | 653 | BootstrapSwitch.prototype._getClasses = function(classes) { 654 | var c, cls, _i, _len; 655 | if (!$.isArray(classes)) { 656 | return ["" + this.options.baseClass + "-" + classes]; 657 | } 658 | cls = []; 659 | for (_i = 0, _len = classes.length; _i < _len; _i++) { 660 | c = classes[_i]; 661 | cls.push("" + this.options.baseClass + "-" + c); 662 | } 663 | return cls; 664 | }; 665 | 666 | return BootstrapSwitch; 667 | 668 | })(); 669 | $.fn.bootstrapSwitch = function() { 670 | var args, option, ret; 671 | option = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 672 | ret = this; 673 | this.each(function() { 674 | var $this, data; 675 | $this = $(this); 676 | data = $this.data("bootstrap-switch"); 677 | if (!data) { 678 | $this.data("bootstrap-switch", data = new BootstrapSwitch(this, option)); 679 | } 680 | if (typeof option === "string") { 681 | return ret = data[option].apply(data, args); 682 | } 683 | }); 684 | return ret; 685 | }; 686 | $.fn.bootstrapSwitch.Constructor = BootstrapSwitch; 687 | return $.fn.bootstrapSwitch.defaults = { 688 | state: true, 689 | size: null, 690 | animate: true, 691 | disabled: false, 692 | readonly: false, 693 | indeterminate: false, 694 | inverse: false, 695 | radioAllOff: false, 696 | onColor: "primary", 697 | offColor: "default", 698 | onText: "ON", 699 | offText: "OFF", 700 | labelText: " ", 701 | handleWidth: "auto", 702 | labelWidth: "auto", 703 | baseClass: "bootstrap-switch", 704 | wrapperClass: "wrapper", 705 | onInit: function() {}, 706 | onSwitchChange: function() {} 707 | }; 708 | })(window.jQuery, window); 709 | 710 | }).call(this); 711 | -------------------------------------------------------------------------------- /public/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.4 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | /*! 8 | * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=9b560b8cf2bcd3a66885) 9 | * Config saved to config.json and https://gist.github.com/9b560b8cf2bcd3a66885 10 | */ 11 | if (typeof jQuery === 'undefined') { 12 | throw new Error('Bootstrap\'s JavaScript requires jQuery') 13 | } 14 | +function ($) { 15 | 'use strict'; 16 | var version = $.fn.jquery.split(' ')[0].split('.') 17 | if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) { 18 | throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher') 19 | } 20 | }(jQuery); 21 | 22 | /* ======================================================================== 23 | * Bootstrap: alert.js v3.3.4 24 | * http://getbootstrap.com/javascript/#alerts 25 | * ======================================================================== 26 | * Copyright 2011-2015 Twitter, Inc. 27 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 28 | * ======================================================================== */ 29 | 30 | 31 | +function ($) { 32 | 'use strict'; 33 | 34 | // ALERT CLASS DEFINITION 35 | // ====================== 36 | 37 | var dismiss = '[data-dismiss="alert"]' 38 | var Alert = function (el) { 39 | $(el).on('click', dismiss, this.close) 40 | } 41 | 42 | Alert.VERSION = '3.3.4' 43 | 44 | Alert.TRANSITION_DURATION = 150 45 | 46 | Alert.prototype.close = function (e) { 47 | var $this = $(this) 48 | var selector = $this.attr('data-target') 49 | 50 | if (!selector) { 51 | selector = $this.attr('href') 52 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 53 | } 54 | 55 | var $parent = $(selector) 56 | 57 | if (e) e.preventDefault() 58 | 59 | if (!$parent.length) { 60 | $parent = $this.closest('.alert') 61 | } 62 | 63 | $parent.trigger(e = $.Event('close.bs.alert')) 64 | 65 | if (e.isDefaultPrevented()) return 66 | 67 | $parent.removeClass('in') 68 | 69 | function removeElement() { 70 | // detach from parent, fire event then clean up data 71 | $parent.detach().trigger('closed.bs.alert').remove() 72 | } 73 | 74 | $.support.transition && $parent.hasClass('fade') ? 75 | $parent 76 | .one('bsTransitionEnd', removeElement) 77 | .emulateTransitionEnd(Alert.TRANSITION_DURATION) : 78 | removeElement() 79 | } 80 | 81 | 82 | // ALERT PLUGIN DEFINITION 83 | // ======================= 84 | 85 | function Plugin(option) { 86 | return this.each(function () { 87 | var $this = $(this) 88 | var data = $this.data('bs.alert') 89 | 90 | if (!data) $this.data('bs.alert', (data = new Alert(this))) 91 | if (typeof option == 'string') data[option].call($this) 92 | }) 93 | } 94 | 95 | var old = $.fn.alert 96 | 97 | $.fn.alert = Plugin 98 | $.fn.alert.Constructor = Alert 99 | 100 | 101 | // ALERT NO CONFLICT 102 | // ================= 103 | 104 | $.fn.alert.noConflict = function () { 105 | $.fn.alert = old 106 | return this 107 | } 108 | 109 | 110 | // ALERT DATA-API 111 | // ============== 112 | 113 | $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) 114 | 115 | }(jQuery); 116 | 117 | /* ======================================================================== 118 | * Bootstrap: button.js v3.3.4 119 | * http://getbootstrap.com/javascript/#buttons 120 | * ======================================================================== 121 | * Copyright 2011-2015 Twitter, Inc. 122 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 123 | * ======================================================================== */ 124 | 125 | 126 | +function ($) { 127 | 'use strict'; 128 | 129 | // BUTTON PUBLIC CLASS DEFINITION 130 | // ============================== 131 | 132 | var Button = function (element, options) { 133 | this.$element = $(element) 134 | this.options = $.extend({}, Button.DEFAULTS, options) 135 | this.isLoading = false 136 | } 137 | 138 | Button.VERSION = '3.3.4' 139 | 140 | Button.DEFAULTS = { 141 | loadingText: 'loading...' 142 | } 143 | 144 | Button.prototype.setState = function (state) { 145 | var d = 'disabled' 146 | var $el = this.$element 147 | var val = $el.is('input') ? 'val' : 'html' 148 | var data = $el.data() 149 | 150 | state = state + 'Text' 151 | 152 | if (data.resetText == null) $el.data('resetText', $el[val]()) 153 | 154 | // push to event loop to allow forms to submit 155 | setTimeout($.proxy(function () { 156 | $el[val](data[state] == null ? this.options[state] : data[state]) 157 | 158 | if (state == 'loadingText') { 159 | this.isLoading = true 160 | $el.addClass(d).attr(d, d) 161 | } else if (this.isLoading) { 162 | this.isLoading = false 163 | $el.removeClass(d).removeAttr(d) 164 | } 165 | }, this), 0) 166 | } 167 | 168 | Button.prototype.toggle = function () { 169 | var changed = true 170 | var $parent = this.$element.closest('[data-toggle="buttons"]') 171 | 172 | if ($parent.length) { 173 | var $input = this.$element.find('input') 174 | if ($input.prop('type') == 'radio') { 175 | if ($input.prop('checked') && this.$element.hasClass('active')) changed = false 176 | else $parent.find('.active').removeClass('active') 177 | } 178 | if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change') 179 | } else { 180 | this.$element.attr('aria-pressed', !this.$element.hasClass('active')) 181 | } 182 | 183 | if (changed) this.$element.toggleClass('active') 184 | } 185 | 186 | 187 | // BUTTON PLUGIN DEFINITION 188 | // ======================== 189 | 190 | function Plugin(option) { 191 | return this.each(function () { 192 | var $this = $(this) 193 | var data = $this.data('bs.button') 194 | var options = typeof option == 'object' && option 195 | 196 | if (!data) $this.data('bs.button', (data = new Button(this, options))) 197 | 198 | if (option == 'toggle') data.toggle() 199 | else if (option) data.setState(option) 200 | }) 201 | } 202 | 203 | var old = $.fn.button 204 | 205 | $.fn.button = Plugin 206 | $.fn.button.Constructor = Button 207 | 208 | 209 | // BUTTON NO CONFLICT 210 | // ================== 211 | 212 | $.fn.button.noConflict = function () { 213 | $.fn.button = old 214 | return this 215 | } 216 | 217 | 218 | // BUTTON DATA-API 219 | // =============== 220 | 221 | $(document) 222 | .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { 223 | var $btn = $(e.target) 224 | if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') 225 | Plugin.call($btn, 'toggle') 226 | e.preventDefault() 227 | }) 228 | .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { 229 | $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) 230 | }) 231 | 232 | }(jQuery); 233 | 234 | /* ======================================================================== 235 | * Bootstrap: carousel.js v3.3.4 236 | * http://getbootstrap.com/javascript/#carousel 237 | * ======================================================================== 238 | * Copyright 2011-2015 Twitter, Inc. 239 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 240 | * ======================================================================== */ 241 | 242 | 243 | +function ($) { 244 | 'use strict'; 245 | 246 | // CAROUSEL CLASS DEFINITION 247 | // ========================= 248 | 249 | var Carousel = function (element, options) { 250 | this.$element = $(element) 251 | this.$indicators = this.$element.find('.carousel-indicators') 252 | this.options = options 253 | this.paused = null 254 | this.sliding = null 255 | this.interval = null 256 | this.$active = null 257 | this.$items = null 258 | 259 | this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) 260 | 261 | this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element 262 | .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) 263 | .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) 264 | } 265 | 266 | Carousel.VERSION = '3.3.4' 267 | 268 | Carousel.TRANSITION_DURATION = 600 269 | 270 | Carousel.DEFAULTS = { 271 | interval: 5000, 272 | pause: 'hover', 273 | wrap: true, 274 | keyboard: true 275 | } 276 | 277 | Carousel.prototype.keydown = function (e) { 278 | if (/input|textarea/i.test(e.target.tagName)) return 279 | switch (e.which) { 280 | case 37: this.prev(); break 281 | case 39: this.next(); break 282 | default: return 283 | } 284 | 285 | e.preventDefault() 286 | } 287 | 288 | Carousel.prototype.cycle = function (e) { 289 | e || (this.paused = false) 290 | 291 | this.interval && clearInterval(this.interval) 292 | 293 | this.options.interval 294 | && !this.paused 295 | && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) 296 | 297 | return this 298 | } 299 | 300 | Carousel.prototype.getItemIndex = function (item) { 301 | this.$items = item.parent().children('.item') 302 | return this.$items.index(item || this.$active) 303 | } 304 | 305 | Carousel.prototype.getItemForDirection = function (direction, active) { 306 | var activeIndex = this.getItemIndex(active) 307 | var willWrap = (direction == 'prev' && activeIndex === 0) 308 | || (direction == 'next' && activeIndex == (this.$items.length - 1)) 309 | if (willWrap && !this.options.wrap) return active 310 | var delta = direction == 'prev' ? -1 : 1 311 | var itemIndex = (activeIndex + delta) % this.$items.length 312 | return this.$items.eq(itemIndex) 313 | } 314 | 315 | Carousel.prototype.to = function (pos) { 316 | var that = this 317 | var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) 318 | 319 | if (pos > (this.$items.length - 1) || pos < 0) return 320 | 321 | if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid" 322 | if (activeIndex == pos) return this.pause().cycle() 323 | 324 | return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos)) 325 | } 326 | 327 | Carousel.prototype.pause = function (e) { 328 | e || (this.paused = true) 329 | 330 | if (this.$element.find('.next, .prev').length && $.support.transition) { 331 | this.$element.trigger($.support.transition.end) 332 | this.cycle(true) 333 | } 334 | 335 | this.interval = clearInterval(this.interval) 336 | 337 | return this 338 | } 339 | 340 | Carousel.prototype.next = function () { 341 | if (this.sliding) return 342 | return this.slide('next') 343 | } 344 | 345 | Carousel.prototype.prev = function () { 346 | if (this.sliding) return 347 | return this.slide('prev') 348 | } 349 | 350 | Carousel.prototype.slide = function (type, next) { 351 | var $active = this.$element.find('.item.active') 352 | var $next = next || this.getItemForDirection(type, $active) 353 | var isCycling = this.interval 354 | var direction = type == 'next' ? 'left' : 'right' 355 | var that = this 356 | 357 | if ($next.hasClass('active')) return (this.sliding = false) 358 | 359 | var relatedTarget = $next[0] 360 | var slideEvent = $.Event('slide.bs.carousel', { 361 | relatedTarget: relatedTarget, 362 | direction: direction 363 | }) 364 | this.$element.trigger(slideEvent) 365 | if (slideEvent.isDefaultPrevented()) return 366 | 367 | this.sliding = true 368 | 369 | isCycling && this.pause() 370 | 371 | if (this.$indicators.length) { 372 | this.$indicators.find('.active').removeClass('active') 373 | var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) 374 | $nextIndicator && $nextIndicator.addClass('active') 375 | } 376 | 377 | var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid" 378 | if ($.support.transition && this.$element.hasClass('slide')) { 379 | $next.addClass(type) 380 | $next[0].offsetWidth // force reflow 381 | $active.addClass(direction) 382 | $next.addClass(direction) 383 | $active 384 | .one('bsTransitionEnd', function () { 385 | $next.removeClass([type, direction].join(' ')).addClass('active') 386 | $active.removeClass(['active', direction].join(' ')) 387 | that.sliding = false 388 | setTimeout(function () { 389 | that.$element.trigger(slidEvent) 390 | }, 0) 391 | }) 392 | .emulateTransitionEnd(Carousel.TRANSITION_DURATION) 393 | } else { 394 | $active.removeClass('active') 395 | $next.addClass('active') 396 | this.sliding = false 397 | this.$element.trigger(slidEvent) 398 | } 399 | 400 | isCycling && this.cycle() 401 | 402 | return this 403 | } 404 | 405 | 406 | // CAROUSEL PLUGIN DEFINITION 407 | // ========================== 408 | 409 | function Plugin(option) { 410 | return this.each(function () { 411 | var $this = $(this) 412 | var data = $this.data('bs.carousel') 413 | var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) 414 | var action = typeof option == 'string' ? option : options.slide 415 | 416 | if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) 417 | if (typeof option == 'number') data.to(option) 418 | else if (action) data[action]() 419 | else if (options.interval) data.pause().cycle() 420 | }) 421 | } 422 | 423 | var old = $.fn.carousel 424 | 425 | $.fn.carousel = Plugin 426 | $.fn.carousel.Constructor = Carousel 427 | 428 | 429 | // CAROUSEL NO CONFLICT 430 | // ==================== 431 | 432 | $.fn.carousel.noConflict = function () { 433 | $.fn.carousel = old 434 | return this 435 | } 436 | 437 | 438 | // CAROUSEL DATA-API 439 | // ================= 440 | 441 | var clickHandler = function (e) { 442 | var href 443 | var $this = $(this) 444 | var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 445 | if (!$target.hasClass('carousel')) return 446 | var options = $.extend({}, $target.data(), $this.data()) 447 | var slideIndex = $this.attr('data-slide-to') 448 | if (slideIndex) options.interval = false 449 | 450 | Plugin.call($target, options) 451 | 452 | if (slideIndex) { 453 | $target.data('bs.carousel').to(slideIndex) 454 | } 455 | 456 | e.preventDefault() 457 | } 458 | 459 | $(document) 460 | .on('click.bs.carousel.data-api', '[data-slide]', clickHandler) 461 | .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler) 462 | 463 | $(window).on('load', function () { 464 | $('[data-ride="carousel"]').each(function () { 465 | var $carousel = $(this) 466 | Plugin.call($carousel, $carousel.data()) 467 | }) 468 | }) 469 | 470 | }(jQuery); 471 | 472 | /* ======================================================================== 473 | * Bootstrap: dropdown.js v3.3.4 474 | * http://getbootstrap.com/javascript/#dropdowns 475 | * ======================================================================== 476 | * Copyright 2011-2015 Twitter, Inc. 477 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 478 | * ======================================================================== */ 479 | 480 | 481 | +function ($) { 482 | 'use strict'; 483 | 484 | // DROPDOWN CLASS DEFINITION 485 | // ========================= 486 | 487 | var backdrop = '.dropdown-backdrop' 488 | var toggle = '[data-toggle="dropdown"]' 489 | var Dropdown = function (element) { 490 | $(element).on('click.bs.dropdown', this.toggle) 491 | } 492 | 493 | Dropdown.VERSION = '3.3.4' 494 | 495 | Dropdown.prototype.toggle = function (e) { 496 | var $this = $(this) 497 | 498 | if ($this.is('.disabled, :disabled')) return 499 | 500 | var $parent = getParent($this) 501 | var isActive = $parent.hasClass('open') 502 | 503 | clearMenus() 504 | 505 | if (!isActive) { 506 | if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { 507 | // if mobile we use a backdrop because click events don't delegate 508 | $('