├── .coveralls.yml ├── .gitignore ├── .hound.yml ├── .rspec ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── Procfile ├── README.md ├── Rakefile ├── VERSION ├── app ├── assets │ ├── images │ │ ├── .keep │ │ └── loading.gif │ ├── javascripts │ │ ├── application.coffee │ │ ├── bootstrap.js.coffee │ │ ├── drag_manager.coffee │ │ ├── kanbans_edit.js.coffee │ │ ├── kanbans_show.js.coffee │ │ ├── top.js.coffee │ │ └── util.js.coffee │ └── stylesheets │ │ ├── application.css │ │ ├── bootstrap_and_overrides.css.less │ │ ├── kanbans_edit.css.scss │ │ ├── kanbans_show.css.scss │ │ └── top.css.scss ├── controllers │ ├── application_controller.rb │ ├── concerns │ │ └── .keep │ ├── issues_controller.rb │ ├── kanbans_controller.rb │ ├── sessions_controller.rb │ └── top_controller.rb ├── helpers │ ├── application_helper.rb │ ├── issues_helper.rb │ ├── kanbans_helper.rb │ ├── people_helper.rb │ └── top_helper.rb ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── concerns │ │ └── .keep │ ├── kanban.rb │ ├── label.rb │ ├── user.rb │ └── user_kanban.rb └── views │ ├── kanbans │ ├── edit.html.slim │ ├── show.html.slim │ └── show.json.jbuilder │ ├── layouts │ └── application.html.slim │ ├── shared │ └── _issue_panel.html.slim │ └── top │ ├── _logged_in.html.slim │ ├── _not_logged_in.html.slim │ └── index.html.slim ├── bin ├── bundle ├── rails ├── rake └── unicorn ├── config.ru ├── config ├── application.rb ├── boot.rb ├── database.yml.mysql ├── database.yml.postgresql ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── gitlab.yml.sample ├── initializers │ ├── backtrace_silencers.rb │ ├── cookies_serializer.rb │ ├── exception_full_backtrace.rb │ ├── filter_parameter_logging.rb │ ├── friendly_id.rb │ ├── gitlab.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── pusher.rb │ ├── secret_token.rb │ ├── session_store.rb │ ├── url_for_with_unescape_id.rb │ └── wrap_parameters.rb ├── locales │ ├── en.bootstrap.yml │ ├── en.yml │ ├── ja.bootstrap.yml │ └── ja.yml ├── pusher.yml.sample ├── routes.rb └── unicorn.rb ├── db ├── migrate │ ├── 20140110175259_create_users.rb │ ├── 20140112144617_create_friendly_id_slugs.rb │ ├── 20140112145704_create_kanbans.rb │ └── 20140112164427_create_labels.rb └── seeds.rb ├── lib ├── assets │ └── .keep ├── support │ ├── init.d │ │ └── unicorn_gitpeach │ ├── logrotate.d │ │ └── gitpeach │ └── nginx │ │ └── gitpeach └── tasks │ ├── .keep │ └── auto_annotate_models.rake ├── log └── .keep ├── public ├── 404.html ├── 422.html ├── 500.html ├── favicon.ico ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff ├── javascripts │ ├── jquery.js │ ├── jquery.min.js │ └── jquery_ujs.js └── robots.txt ├── script ├── build_for_jenkins.sh └── plot-rspec-slowest-examples.rb ├── shots ├── gitpeach.gif ├── issue.png └── pusher.png ├── spec ├── controllers │ ├── issues_controller_spec.rb │ ├── kanbans_controller_spec.rb │ ├── sessions_controller_spec.rb │ └── top_controller_spec.rb ├── factories │ ├── kanbans.rb │ ├── labels.rb │ ├── sequences.rb │ └── users.rb ├── helpers │ └── application_helper_spec.rb ├── models │ └── kanban_spec.rb ├── spec_helper.rb └── support │ ├── shared_contexts │ └── using_gitlab_mock.rb │ └── shared_examples │ └── a_user.rb ├── tmp └── .keep └── vendor └── assets ├── javascripts └── .keep └── stylesheets ├── .keep └── 1pxdeep ├── 1pxdeep.less └── scheme.less /.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: A4Ghae9PeorXsNQuenOQfqdGXtgbYgTnm -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #---------------------------------------------------------------------------- 2 | # Ignore these files when commiting to a git repository. 3 | # 4 | # See http://help.github.com/ignore-files/ for more about ignoring files. 5 | # 6 | # The original version of this file is found here: 7 | # https://github.com/RailsApps/rails-composer/blob/master/files/gitignore.txt 8 | # 9 | # Corrections? Improvements? Create a GitHub issue: 10 | # http://github.com/RailsApps/rails-composer/issues 11 | #---------------------------------------------------------------------------- 12 | 13 | # bundler state 14 | /.bundle 15 | /vendor/bundle/ 16 | /vendor/ruby/ 17 | 18 | # minimal Rails specific artifacts 19 | db/*.sqlite3 20 | /db/*.sqlite3-journal 21 | /log/* 22 | /tmp/* 23 | 24 | # configuration file introduced in Rails 4.1 25 | /config/secrets.yml 26 | 27 | # various artifacts 28 | **.war 29 | *.rbc 30 | *.sassc 31 | .redcar/ 32 | .sass-cache 33 | /config/config.yml 34 | /config/database.yml 35 | /coverage.data 36 | /coverage/ 37 | /db/*.javadb/ 38 | /db/*.sqlite3 39 | /doc/api/ 40 | /doc/app/ 41 | /doc/features.html 42 | /doc/specs.html 43 | /public/cache 44 | /public/stylesheets/compiled 45 | /public/system/* 46 | /spec/tmp/* 47 | /cache 48 | /capybara* 49 | /capybara-*.html 50 | /gems 51 | /specifications 52 | rerun.txt 53 | pickle-email-*.html 54 | .zeus.sock 55 | 56 | # If you find yourself ignoring temporary files generated by your text editor 57 | # or operating system, you probably want to add a global ignore instead: 58 | # git config --global core.excludesfile ~/.gitignore_global 59 | # 60 | # Here are some files you may want to ignore globally: 61 | 62 | # scm revert files 63 | **.orig 64 | 65 | # Mac finder artifacts 66 | .DS_Store 67 | 68 | # Netbeans project directory 69 | /nbproject/ 70 | 71 | # RubyMine project files 72 | .idea 73 | 74 | # Textmate project files 75 | /*.tmproj 76 | 77 | # vim artifacts 78 | **.swp 79 | /reports/ 80 | /coverage/ 81 | /db/schema.rb 82 | 83 | config/gitlab.yml 84 | pusher.yml 85 | -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | Style/LineLength: 2 | Description: 'Limit lines to 130 characters.' 3 | Max: 130 4 | Style/SpaceInsideParens: 5 | Enabled: false 6 | Style/SpaceBeforeBlockBraces: 7 | Enabled: false 8 | StringLiterals: 9 | Enabled: false 10 | Style/TrailingComma: 11 | Enabled: false 12 | Style/BlockComments: 13 | Enabled: false 14 | Style/NilComparison: 15 | Enabled: false 16 | Style/Documentation: 17 | Enabled: false 18 | Style/RegexpLiteral: 19 | Enabled: false 20 | Style/SignalException: 21 | Enabled: false 22 | Style/CaseEquality: 23 | Enabled: false 24 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.0 4 | - 2.1 5 | - 2.2 6 | env: 7 | - DB=mysql 8 | - DB=postgresql 9 | cache: bundler 10 | before_script: 11 | - "cp config/database.yml.$DB config/database.yml" 12 | - "cp config/gitlab.yml.sample config/gitlab.yml" 13 | - "cp config/pusher.yml.sample config/pusher.yml" 14 | - "RAILS_ENV=test bundle exec rake db:create db:migrate" 15 | script: bundle exec rspec 16 | branches: 17 | only: 18 | - master 19 | notifications: 20 | email: false 21 | sudo: false 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## master 2 | [full changelog](http://github.com/sue445/gitpeach/compare/0.0.7...master) 3 | 4 | ## 0.0.7 5 | [full changelog](http://github.com/sue445/gitpeach/compare/0.0.6...0.0.7) 6 | 7 | * upgrade to rails 4.1.4 8 | 9 | ## 0.0.6 10 | [full changelog](http://github.com/sue445/gitpeach/compare/0.0.5...0.0.6) 11 | 12 | * Support [Heroku](http://www.heroku.com/) 13 | 14 | ## 0.0.5 15 | [full changelog](http://github.com/sue445/gitpeach/compare/0.0.4...0.0.5) 16 | 17 | * upgrade to rails 4.1.1 18 | * Refactor: js -> coffeescript 19 | 20 | ## 0.0.4 21 | [full changelog](http://github.com/sue445/gitpeach/compare/0.0.3...0.0.4) 22 | 23 | * upgrade to rails 4.0.4 24 | 25 | ## 0.0.3 26 | [full changelog](http://github.com/sue445/gitpeach/compare/0.0.2...0.0.3) 27 | 28 | * upgrade to rails 4.0.3 29 | * bugfix: Can not remove label 30 | * https://github.com/sue445/gitpeach/issues/3 31 | 32 | ## 0.0.2 33 | [full changelog](http://github.com/sue445/gitpeach/compare/0.0.1...0.0.2) 34 | 35 | * tweak UI 36 | * show issue timestamp 37 | 38 | ## 0.0.1 39 | * first release 40 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # uncomment out when deploy to Heroku 4 | # ruby "2.1.2" 5 | 6 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 7 | gem 'rails', '4.1.6' 8 | 9 | # Support DBs 10 | gem 'mysql2', "~> 0.3.15", group: :mysql 11 | gem 'pg', group: :postgres 12 | 13 | gem "avatar", "~> 0.2.0" 14 | gem 'coffee-rails', '~> 4.1.0' 15 | gem "friendly_id", "~> 5.0.3" 16 | gem "gitlab", "~> 3.2.0" 17 | gem 'jbuilder', '~> 2.2.3' 18 | gem 'jquery-rails', "~> 3.1.2" 19 | gem "jquery-ui-rails", "~> 5.0.2" 20 | gem "less-rails", "2.5.0" 21 | gem "libv8", "~> 3.16.14.7" 22 | gem 'pusher', "~> 0.14.2" 23 | gem 'sass-rails', '~> 4.0.3' 24 | gem "slim-rails", "~> 2.1.5" 25 | gem "therubyracer", "~> 0.12.1", platform: :ruby 26 | gem "twitter-bootstrap-rails", github: "seyhunak/twitter-bootstrap-rails", branch: "bootstrap3", ref: "128f37" 27 | gem 'uglifier', '~> 2.5.3' 28 | 29 | group :development do 30 | gem "annotate", "~> 2.6.5", require: false 31 | gem "better_errors", '~> 2.0.0' 32 | gem "binding_of_caller" 33 | gem "net-http-spy" 34 | gem "pry" , "~> 0.10.1" , group: :test 35 | gem "pry-remote", "~> 0.1.8" , group: :test 36 | gem "pry-nav" , "~> 0.2.4" , group: :test 37 | gem "pry-rails" , "~> 0.3.2" , group: :test 38 | gem "view_source_map", "0.1.0" 39 | end 40 | 41 | group :test do 42 | gem 'coveralls', '~> 0.7.1', require: false 43 | gem "database_rewinder", "~> 0.4.1" 44 | gem "factory_girl_rails", "~> 4.5.0", group: :development 45 | gem "rspec-collection_matchers", "~> 1.0.0" 46 | gem "rspec-its", "1.0.1" 47 | gem "rspec-parameterized" 48 | gem "rspec-rails", "~> 3.1.0", group: :development 49 | gem "webmock", "~> 1.20.0" 50 | end 51 | 52 | group :production do 53 | gem "rails_12factor", "0.0.3" 54 | gem "unicorn", "~> 4.8.3" 55 | end 56 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/seyhunak/twitter-bootstrap-rails.git 3 | revision: 128f373e151528c9516ea8df32831fc60ac1c35d 4 | ref: 128f37 5 | branch: bootstrap3 6 | specs: 7 | twitter-bootstrap-rails (3.1.1) 8 | actionpack (>= 3.1) 9 | execjs 10 | rails (>= 3.1) 11 | railties (>= 3.1) 12 | 13 | GEM 14 | remote: https://rubygems.org/ 15 | specs: 16 | abstract_type (0.0.7) 17 | actionmailer (4.1.6) 18 | actionpack (= 4.1.6) 19 | actionview (= 4.1.6) 20 | mail (~> 2.5, >= 2.5.4) 21 | actionpack (4.1.6) 22 | actionview (= 4.1.6) 23 | activesupport (= 4.1.6) 24 | rack (~> 1.5.2) 25 | rack-test (~> 0.6.2) 26 | actionview (4.1.6) 27 | activesupport (= 4.1.6) 28 | builder (~> 3.1) 29 | erubis (~> 2.7.0) 30 | activemodel (4.1.6) 31 | activesupport (= 4.1.6) 32 | builder (~> 3.1) 33 | activerecord (4.1.6) 34 | activemodel (= 4.1.6) 35 | activesupport (= 4.1.6) 36 | arel (~> 5.0.0) 37 | activesupport (4.1.6) 38 | i18n (~> 0.6, >= 0.6.9) 39 | json (~> 1.7, >= 1.7.7) 40 | minitest (~> 5.1) 41 | thread_safe (~> 0.1) 42 | tzinfo (~> 1.1) 43 | adamantium (0.2.0) 44 | ice_nine (~> 0.11.0) 45 | memoizable (~> 0.4.0) 46 | addressable (2.3.6) 47 | annotate (2.6.5) 48 | activerecord (>= 2.3.0) 49 | rake (>= 0.8.7) 50 | arel (5.0.1.20140414130214) 51 | ast (2.0.0) 52 | avatar (0.2.0) 53 | better_errors (2.0.0) 54 | coderay (>= 1.0.0) 55 | erubis (>= 2.6.6) 56 | rack (>= 0.9.0) 57 | binding_of_caller (0.7.2) 58 | debug_inspector (>= 0.0.1) 59 | builder (3.2.2) 60 | coderay (1.1.0) 61 | coffee-rails (4.1.0) 62 | coffee-script (>= 2.2.0) 63 | railties (>= 4.0.0, < 5.0) 64 | coffee-script (2.3.0) 65 | coffee-script-source 66 | execjs 67 | coffee-script-source (1.8.0) 68 | commonjs (0.2.7) 69 | concord (0.1.5) 70 | adamantium (~> 0.2.0) 71 | equalizer (~> 0.0.9) 72 | coveralls (0.7.1) 73 | multi_json (~> 1.3) 74 | rest-client 75 | simplecov (>= 0.7) 76 | term-ansicolor 77 | thor 78 | crack (0.4.2) 79 | safe_yaml (~> 1.0.0) 80 | database_rewinder (0.4.1) 81 | debug_inspector (0.0.2) 82 | diff-lcs (1.2.5) 83 | docile (1.1.5) 84 | equalizer (0.0.9) 85 | erubis (2.7.0) 86 | execjs (2.2.2) 87 | factory_girl (4.5.0) 88 | activesupport (>= 3.0.0) 89 | factory_girl_rails (4.5.0) 90 | factory_girl (~> 4.5.0) 91 | railties (>= 3.0.0) 92 | friendly_id (5.0.4) 93 | activerecord (>= 4.0.0) 94 | gitlab (3.2.0) 95 | httparty 96 | terminal-table 97 | hike (1.2.3) 98 | httparty (0.13.1) 99 | json (~> 1.8) 100 | multi_xml (>= 0.5.2) 101 | httpclient (2.5.1) 102 | i18n (0.6.11) 103 | ice_nine (0.11.1) 104 | jbuilder (2.2.3) 105 | activesupport (>= 3.0.0, < 5) 106 | multi_json (~> 1.2) 107 | jquery-rails (3.1.2) 108 | railties (>= 3.0, < 5.0) 109 | thor (>= 0.14, < 2.0) 110 | jquery-ui-rails (5.0.2) 111 | railties (>= 3.2.16) 112 | json (1.8.1) 113 | kgio (2.9.2) 114 | less (2.5.1) 115 | commonjs (~> 0.2.7) 116 | less-rails (2.5.0) 117 | actionpack (>= 3.1) 118 | less (~> 2.5.0) 119 | libv8 (3.16.14.7) 120 | mail (2.6.1) 121 | mime-types (>= 1.16, < 3) 122 | memoizable (0.4.2) 123 | thread_safe (~> 0.3, >= 0.3.1) 124 | method_source (0.8.2) 125 | mime-types (2.4.3) 126 | minitest (5.4.2) 127 | multi_json (1.10.1) 128 | multi_xml (0.5.5) 129 | mysql2 (0.3.16) 130 | net-http-spy (0.2.1) 131 | netrc (0.8.0) 132 | parser (2.2.0) 133 | ast (>= 1.1, < 3.0) 134 | slop (~> 3.4, >= 3.4.5) 135 | pg (0.17.1) 136 | proc_to_ast (0.0.7) 137 | coderay 138 | parser 139 | unparser 140 | procto (0.0.2) 141 | pry (0.10.1) 142 | coderay (~> 1.1.0) 143 | method_source (~> 0.8.1) 144 | slop (~> 3.4) 145 | pry-nav (0.2.4) 146 | pry (>= 0.9.10, < 0.11.0) 147 | pry-rails (0.3.2) 148 | pry (>= 0.9.10) 149 | pry-remote (0.1.8) 150 | pry (~> 0.9) 151 | slop (~> 3.0) 152 | pusher (0.14.2) 153 | httpclient (~> 2.4) 154 | multi_json (~> 1.0) 155 | signature (~> 0.1.6) 156 | rack (1.5.2) 157 | rack-test (0.6.2) 158 | rack (>= 1.0) 159 | rails (4.1.6) 160 | actionmailer (= 4.1.6) 161 | actionpack (= 4.1.6) 162 | actionview (= 4.1.6) 163 | activemodel (= 4.1.6) 164 | activerecord (= 4.1.6) 165 | activesupport (= 4.1.6) 166 | bundler (>= 1.3.0, < 2.0) 167 | railties (= 4.1.6) 168 | sprockets-rails (~> 2.0) 169 | rails_12factor (0.0.3) 170 | rails_serve_static_assets 171 | rails_stdout_logging 172 | rails_serve_static_assets (0.0.2) 173 | rails_stdout_logging (0.0.3) 174 | railties (4.1.6) 175 | actionpack (= 4.1.6) 176 | activesupport (= 4.1.6) 177 | rake (>= 0.8.7) 178 | thor (>= 0.18.1, < 2.0) 179 | raindrops (0.13.0) 180 | rake (10.3.2) 181 | ref (1.0.5) 182 | rest-client (1.7.2) 183 | mime-types (>= 1.16, < 3.0) 184 | netrc (~> 0.7) 185 | rspec (3.1.0) 186 | rspec-core (~> 3.1.0) 187 | rspec-expectations (~> 3.1.0) 188 | rspec-mocks (~> 3.1.0) 189 | rspec-collection_matchers (1.0.0) 190 | rspec-expectations (>= 2.99.0.beta1) 191 | rspec-core (3.1.7) 192 | rspec-support (~> 3.1.0) 193 | rspec-expectations (3.1.2) 194 | diff-lcs (>= 1.2.0, < 2.0) 195 | rspec-support (~> 3.1.0) 196 | rspec-its (1.0.1) 197 | rspec-core (>= 2.99.0.beta1) 198 | rspec-expectations (>= 2.99.0.beta1) 199 | rspec-mocks (3.1.3) 200 | rspec-support (~> 3.1.0) 201 | rspec-parameterized (0.1.2) 202 | binding_of_caller 203 | parser 204 | proc_to_ast 205 | rspec (>= 2.13, < 4) 206 | unparser 207 | rspec-rails (3.1.0) 208 | actionpack (>= 3.0) 209 | activesupport (>= 3.0) 210 | railties (>= 3.0) 211 | rspec-core (~> 3.1.0) 212 | rspec-expectations (~> 3.1.0) 213 | rspec-mocks (~> 3.1.0) 214 | rspec-support (~> 3.1.0) 215 | rspec-support (3.1.2) 216 | safe_yaml (1.0.4) 217 | sass (3.2.19) 218 | sass-rails (4.0.3) 219 | railties (>= 4.0.0, < 5.0) 220 | sass (~> 3.2.0) 221 | sprockets (~> 2.8, <= 2.11.0) 222 | sprockets-rails (~> 2.0) 223 | signature (0.1.7) 224 | simplecov (0.9.1) 225 | docile (~> 1.1.0) 226 | multi_json (~> 1.0) 227 | simplecov-html (~> 0.8.0) 228 | simplecov-html (0.8.0) 229 | slim (2.1.0) 230 | temple (~> 0.6.9) 231 | tilt (>= 1.3.3, < 2.1) 232 | slim-rails (2.1.5) 233 | actionpack (>= 3.0, < 4.2) 234 | activesupport (>= 3.0, < 4.2) 235 | railties (>= 3.0, < 4.2) 236 | slim (~> 2.0) 237 | slop (3.6.0) 238 | sprockets (2.11.0) 239 | hike (~> 1.2) 240 | multi_json (~> 1.0) 241 | rack (~> 1.0) 242 | tilt (~> 1.1, != 1.3.0) 243 | sprockets-rails (2.2.0) 244 | actionpack (>= 3.0) 245 | activesupport (>= 3.0) 246 | sprockets (>= 2.8, < 4.0) 247 | temple (0.6.9) 248 | term-ansicolor (1.3.0) 249 | tins (~> 1.0) 250 | terminal-table (1.4.5) 251 | therubyracer (0.12.1) 252 | libv8 (~> 3.16.14.0) 253 | ref 254 | thor (0.19.1) 255 | thread_safe (0.3.4) 256 | tilt (1.4.1) 257 | tins (1.3.3) 258 | tzinfo (1.2.2) 259 | thread_safe (~> 0.1) 260 | uglifier (2.5.3) 261 | execjs (>= 0.3.0) 262 | json (>= 1.8.0) 263 | unicorn (4.8.3) 264 | kgio (~> 2.6) 265 | rack 266 | raindrops (~> 0.7) 267 | unparser (0.1.16) 268 | abstract_type (~> 0.0.7) 269 | adamantium (~> 0.2.0) 270 | concord (~> 0.1.5) 271 | equalizer (~> 0.0.9) 272 | parser (~> 2.2.0.pre.7) 273 | procto (~> 0.0.2) 274 | view_source_map (0.1.0) 275 | rails (>= 3.2) 276 | webmock (1.20.0) 277 | addressable (>= 2.3.6) 278 | crack (>= 0.3.2) 279 | 280 | PLATFORMS 281 | ruby 282 | 283 | DEPENDENCIES 284 | annotate (~> 2.6.5) 285 | avatar (~> 0.2.0) 286 | better_errors (~> 2.0.0) 287 | binding_of_caller 288 | coffee-rails (~> 4.1.0) 289 | coveralls (~> 0.7.1) 290 | database_rewinder (~> 0.4.1) 291 | factory_girl_rails (~> 4.5.0) 292 | friendly_id (~> 5.0.3) 293 | gitlab (~> 3.2.0) 294 | jbuilder (~> 2.2.3) 295 | jquery-rails (~> 3.1.2) 296 | jquery-ui-rails (~> 5.0.2) 297 | less-rails (= 2.5.0) 298 | libv8 (~> 3.16.14.7) 299 | mysql2 (~> 0.3.15) 300 | net-http-spy 301 | pg 302 | pry (~> 0.10.1) 303 | pry-nav (~> 0.2.4) 304 | pry-rails (~> 0.3.2) 305 | pry-remote (~> 0.1.8) 306 | pusher (~> 0.14.2) 307 | rails (= 4.1.6) 308 | rails_12factor (= 0.0.3) 309 | rspec-collection_matchers (~> 1.0.0) 310 | rspec-its (= 1.0.1) 311 | rspec-parameterized 312 | rspec-rails (~> 3.1.0) 313 | sass-rails (~> 4.0.3) 314 | slim-rails (~> 2.1.5) 315 | therubyracer (~> 0.12.1) 316 | twitter-bootstrap-rails! 317 | uglifier (~> 2.5.3) 318 | unicorn (~> 4.8.3) 319 | view_source_map (= 0.1.0) 320 | webmock (~> 1.20.0) 321 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 sue445 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gitpeach 2 | 3 | [![Build Status](https://travis-ci.org/sue445/gitpeach.png)](https://travis-ci.org/sue445/gitpeach) 4 | [![Coverage Status](https://coveralls.io/repos/sue445/gitpeach/badge.png)](https://coveralls.io/r/sue445/gitpeach) 5 | [![Dependency Status](https://gemnasium.com/sue445/gitpeach.png)](https://gemnasium.com/sue445/gitpeach) 6 | [![Code Climate](https://codeclimate.com/github/sue445/gitpeach.png)](https://codeclimate.com/github/sue445/gitpeach) 7 | [![Inline docs](http://inch-ci.org/github/sue445/gitpeach.svg?branch=master)](http://inch-ci.org/github/sue445/gitpeach) 8 | 9 | [waffle.io](https://waffle.io/) clone for [Gitlab](http://gitlab.org/) 10 | 11 | ![Gitpeach](https://raw.github.com/sue445/gitpeach/master/shots/gitpeach.gif) 12 | 13 | ## Requirements 14 | * Gitlab API 5.3.0+ and 6.0.x and 6.2.0+ 15 | * **only 6.1.0** is not supported 16 | * ruby 2.0.0+ 17 | * MySQL or PostgreSQL 18 | * Pusher 19 | 20 | ## Setup 21 | ### Signup to [Pusher](https://app.pusher.com/) 22 | Create new app 23 | 24 | ![Pusher](https://raw.github.com/sue445/gitpeach/master/shots/pusher.png) 25 | 26 | ### Setup command 27 | ```sh 28 | bundle install --path vendor/bundle 29 | 30 | # Mysql 31 | cp config/database.yml{.mysql,} 32 | 33 | # PostgreSQL 34 | cp config/database.yml{.postgresql,} 35 | 36 | cp config/gitlab.yml{.sample,} 37 | cp config/pusher.yml{.sample,} 38 | vi config/database.yml 39 | vi config/gitlab.yml 40 | vi config/pusher.yml 41 | bundle exec rake db:create 42 | bundle exec rake db:migrate RAILS_ENV=development 43 | 44 | bundle exec rails s 45 | open http://localhost:3000/ 46 | ``` 47 | 48 | ## Test 49 | ```sh 50 | bundle exec rake db:migrate RAILS_ENV=test 51 | bundle exec rspec 52 | ``` 53 | 54 | ## Sample scripts 55 | see [lib/support](lib/support) 56 | 57 | ## Production (example) 58 | ### Setup 59 | ```sh 60 | cd /path/to 61 | git clone git@github.com:sue445/gitpeach.git 62 | cd gitpeach 63 | 64 | cp config/database.yml{.mysql,} 65 | bundle install --path vendor/bundle --without development test postgres 66 | # or 67 | cp config/database.yml{.postgresql,} 68 | bundle install --path vendor/bundle --without development test mysql 69 | 70 | cp config/gitlab.yml{.sample,} 71 | cp config/pusher.yml{.sample,} 72 | vi config/database.yml 73 | vi config/gitlab.yml 74 | vi config/pusher.yml 75 | 76 | RAILS_ENV=production bundle exec db:create rake db:migrate 77 | 78 | sudo cp /path/to/gitpeach/lib/support/nginx/gitpeach /etc/nginx/sites-enabled/gitpeach 79 | sudo vi /etc/nginx/sites-enabled/gitpeach 80 | 81 | sudo cp /path/to/gitpeach/lib/support/init.d/unicorn_gitpeach /etc/init.d/unicorn_gitpeach 82 | sudo /etc/init.d/unicorn_gitpeach start 83 | 84 | sudo /etc/init.d/nginx reload 85 | 86 | open http://peach.your-site.com/ 87 | ``` 88 | 89 | ### Deploy 90 | ```sh 91 | cd /path/to/gitpeach 92 | git pull --ff 93 | bundle install --path vendor/bundle --without development test postgres 94 | RAILS_ENV=production bundle exec rake assets:clean assets:precompile 95 | RAILS_ENV=production bundle exec rake db:migrate 96 | sudo /etc/init.d/unicorn_gitpeach restart 97 | ``` 98 | 99 | ## Production (Heroku) 100 | ```sh 101 | heroku create 102 | git checkout -b deploy 103 | 104 | vi Gemfile 105 | # uncomment out this 106 | # ruby "2.1.2" 107 | 108 | bundle install --without mysql 109 | git add -f config/gitlab.yml 110 | git add -f config/pusher.yml 111 | git commit -am "Add setting for heroku" 112 | 113 | # push deploy branch as master branch 114 | git push heroku deploy:master 115 | 116 | heroku run rake db:migrate 117 | heroku open 118 | ``` 119 | 120 | ## FAQ 121 | ### Q. Difference with waffle.io 122 | 1. realtime updates 123 | * using websocket 124 | 2. show milestone and timestamp 125 | * ![Milestone](https://raw.github.com/sue445/gitpeach/master/shots/issue.png) 126 | 127 | ### Q. Why Peach? 128 | A. Gitlab -> Git love -> Momozono Love -> Cure Peach 129 | 130 | Detail: [Fresh Pretty Cure! - Wikipedia](http://en.wikipedia.org/wiki/Fresh_Pretty_Cure!) 131 | 132 | ### Q. I want to change color 133 | 1. open [bootstrap_and_overrides.css.less](app/assets/stylesheets/bootstrap_and_overrides.css.less) 134 | 2. edit `@seed-color` 135 | * ref. http://rriepe.github.io/1pxdeep/ 136 | 137 | 138 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/sue445/gitpeach/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 139 | 140 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Gitpeach::Application.load_tasks 7 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.0.7 2 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sue445/gitpeach/08096149c8d768c98159c9061cb324a4b2663be1/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sue445/gitpeach/08096149c8d768c98159c9061cb324a4b2663be1/app/assets/images/loading.gif -------------------------------------------------------------------------------- /app/assets/javascripts/application.coffee: -------------------------------------------------------------------------------- 1 | # This is a manifest file that'll be compiled into application.js, which will include all the files 2 | # listed below. 3 | # 4 | # Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | # or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | # 7 | # It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | # compiled file. 9 | # 10 | # Read Sprockets README (https:#github.com/sstephenson/sprockets#sprockets-directives) for details 11 | # about supported directives. 12 | # 13 | #= require jquery 14 | #= require jquery_ujs 15 | #= require twitter/bootstrap 16 | # 17 | # UI Core 18 | #= require jquery-ui/core 19 | #= require jquery-ui/widget 20 | #= require jquery-ui/mouse 21 | #= require jquery-ui/position 22 | # 23 | # Interactions 24 | #= require jquery-ui/draggable 25 | #= require jquery-ui/droppable 26 | #= require jquery-ui/sortable 27 | # 28 | #= stub kanbans_show 29 | #= stub kanbans_edit 30 | #= stub top 31 | # 32 | #= require_tree . 33 | -------------------------------------------------------------------------------- /app/assets/javascripts/bootstrap.js.coffee: -------------------------------------------------------------------------------- 1 | jQuery -> 2 | $("a[rel~=popover], .has-popover").popover() 3 | $("a[rel~=tooltip], .has-tooltip").tooltip() 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/drag_manager.coffee: -------------------------------------------------------------------------------- 1 | # via. http://jsdo.it/tgaisho/draggable1 2 | 3 | class DragManager 4 | # instance variables 5 | target_id = null 6 | original_ui = null 7 | 8 | # same to kanban.css.scss 9 | issue_panel_default_z_index = 50 10 | 11 | # public methods 12 | drag_start: (event, ui) => 13 | target_id = event.target.id 14 | original_ui = ui 15 | 16 | target = $("#" + target_id) 17 | $(".ui-draggable").css("z-index", issue_panel_default_z_index) 18 | target.css("z-index", 100); 19 | 20 | drag_end: (event, ui) => 21 | target = $("#" + target_id) 22 | 23 | draggables = $.grep($(".ui-draggable"), (v, i) -> 24 | v = $(v) 25 | # reject own 26 | return (v.attr("id") && v.attr("id") != target_id) 27 | ) 28 | 29 | is_conflict = is_conflict_position.call( 30 | @, 31 | target.get(0).getBoundingClientRect(), 32 | {width: target.width(), height: target.height()}, 33 | draggables 34 | ) 35 | 36 | if(is_conflict) 37 | rollback() 38 | false 39 | else 40 | true 41 | 42 | rollback: => 43 | target = $("#" + target_id) 44 | 45 | target.css( 46 | left: original_ui.originalPosition.left, 47 | top: original_ui.originalPosition.top 48 | ) 49 | 50 | # private methods 51 | is_conflict_position = (position, size, targets) => 52 | corner_positions = get_corner_positions(position.left, position.top, size.width, size.height) 53 | conflict = false 54 | 55 | $(targets).each (i, v) -> 56 | target_position = $(v).get(0).getBoundingClientRect() 57 | target_corner_positions = get_corner_positions.call(@, target_position.left, target_position.top, $(v).width(), $(v).height()) 58 | 59 | $(corner_positions).each -> 60 | (j, o) -> 61 | if o.left >= target_corner_positions[0].left - 15 && 62 | o.left <= target_corner_positions[1].left + 15 && 63 | o.top >= target_corner_positions[0].top - 15 && 64 | o.top <= target_corner_positions[2].top + 15 65 | conflict = true 66 | 67 | conflict 68 | 69 | get_corner_positions = (left, top, width, height) => 70 | obj = []; 71 | obj.push(left: left , top: top) # left top 72 | obj.push(left: left + width, top: top) # right top 73 | obj.push(left: left , top: top + height) # left down 74 | obj.push(left: left + width, top: top + height) # right down 75 | obj 76 | 77 | window.drag_manager = new DragManager() 78 | -------------------------------------------------------------------------------- /app/assets/javascripts/kanbans_edit.js.coffee: -------------------------------------------------------------------------------- 1 | refresh_table = -> 2 | $("li.edit_label").each -> 3 | is_backlog = $(this).find("input[name='labels[][is_backlog_issue]']").is(':checked') 4 | is_close = $(this).find("input[name='labels[][is_close_issue]']").is(':checked') 5 | 6 | if is_backlog || is_close 7 | # disabled 8 | $(this).find("input[name='labels[][gitlab_label]']").attr('disabled','disabled') 9 | else 10 | # enabled 11 | $(this).find("input[name='labels[][gitlab_label]']").removeAttr('disabled') 12 | 13 | $(document).ready -> 14 | $(".help-tooltip").tooltip() 15 | refresh_table() 16 | 17 | $("li.edit_label input:radio").change -> 18 | refresh_table() 19 | 20 | $("li.edit_label button.delete_label").click -> 21 | $(this).parents("li.edit_label").remove() 22 | 23 | # clear old radio button 24 | $("input[name='labels[][is_backlog_issue]']").click -> 25 | that = this 26 | $("input[name='labels[][is_backlog_issue]']").each -> 27 | $(this).removeAttr("checked") unless that == this 28 | 29 | $("input[name='labels[][is_close_issue]']").click -> 30 | that = this 31 | $("input[name='labels[][is_close_issue]']").each -> 32 | $(this).removeAttr("checked") unless that == this 33 | 34 | $("#add_label").click -> 35 | $("ul#hidden_list li").clone().prependTo($("#update_kanban ul")) 36 | 37 | $("#update_kanban ul").sortable 38 | connectWith: "#update_kanban ul" 39 | cursor: "move" 40 | containment: "#update_kanban ul" 41 | axis: "y" 42 | handle: "i.drag-column" 43 | -------------------------------------------------------------------------------- /app/assets/javascripts/kanbans_show.js.coffee: -------------------------------------------------------------------------------- 1 | issue_id = null 2 | 3 | init_issue_panel = (selector) -> 4 | $(selector).draggable( 5 | scope: "label-column-scope" 6 | revert: "invalid" 7 | stack: "issue-panel" 8 | cursor: "move" 9 | snap: true 10 | handle: ".issue-panel__header" 11 | start: (event, ui) -> 12 | issue_id = event.target.id.replace("issue_", "") 13 | drag_manager.drag_start(event, ui) 14 | null 15 | stop: (event, ui) -> 16 | drag_manager.drag_end(event, ui) 17 | null 18 | ) 19 | $("#{selector} .issue-panel__updated_at").tooltip() 20 | 21 | init_label_column = (selector) -> 22 | $(selector).droppable( 23 | scope: "label-column-scope" 24 | tolerance: "fit" 25 | drop: (event, ui) -> 26 | to_label_id = event.target.id.replace("label_", "") 27 | kanban_name = $("#kanban_name").val() 28 | $.ajax( 29 | url: "/#{kanban_name}/issues/#{issue_id}" 30 | data: 31 | to_label_id: to_label_id 32 | method: "PUT" 33 | error: -> 34 | drag_manager.rollback() 35 | ) 36 | null 37 | ) 38 | 39 | $(document).ready -> 40 | width_rate = 100 / $("#label_groups_count").val() 41 | $(".label-area").css("width", "#{width_rate}%") 42 | 43 | init_issue_panel(".issue-panel") 44 | init_label_column(".label-column") 45 | 46 | pusher = new window.Pusher($("#pusher_key").val()) 47 | channel = pusher.subscribe($("#channel").val()) 48 | channel.bind( 49 | "issue_update_event", 50 | (data) -> 51 | for label_id, issue_ids of data.label_group_ids 52 | do (label_id, issue_ids) -> 53 | $("#count_#{label_id}").text(issue_ids.length) 54 | for issue_id in issue_ids 55 | do (issue_id) -> 56 | $.ajax( 57 | url: "/#{$("#kanban_name").val()}/issues/#{issue_id}" 58 | dataType: "html" 59 | success: (html, data_type) -> 60 | $("#issue_#{issue_id}").remove() 61 | $("#label_#{label_id}").append($(html)) 62 | init_issue_panel("#issue_#{issue_id}") 63 | ) 64 | ) 65 | 66 | $("#create_issue_form").submit -> 67 | if $("#new_issue_title").val() 68 | $("#loading").show() 69 | $("#create_issue_button").attr("disabled", "disabled") 70 | $.ajax( 71 | url: "/#{$("#kanban_name").val()}/issues" 72 | method: "POST" 73 | dataType: "json" 74 | data: 75 | title: $("#new_issue_title").val() 76 | success: (data, data_type) -> 77 | $("#loading").hide() 78 | $("#create_issue_button").removeAttr("disabled") 79 | $("#new_issue_title").val("") 80 | ) 81 | false 82 | -------------------------------------------------------------------------------- /app/assets/javascripts/top.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/util.js.coffee: -------------------------------------------------------------------------------- 1 | window.util = 2 | alert: (message, alert_class="alert-success", is_auto_close=false) -> 3 | $("#alert-area .alert").alert('close') 4 | 5 | close_button = 6 | $(""). 7 | addClass("close"). 8 | attr( 9 | "data-dismiss": "alert" 10 | href: "#" 11 | "aria-hidden": true 12 | ). 13 | html("×") 14 | 15 | $("
"). 16 | addClass("alert"). 17 | addClass(alert_class). 18 | text(message). 19 | append(close_button). 20 | appendTo($("#alert-area")) 21 | 22 | if is_auto_close 23 | window.setTimeout( 24 | -> 25 | $("#alert-area .alert").alert('close') 26 | 2000 27 | ) 28 | 29 | $(document).ready -> 30 | $(document).ajaxError (event, jqxhr, settings, exception) -> 31 | if jqxhr.responseJSON 32 | json = jqxhr.responseJSON 33 | util.alert("#{json.exception} #{json.message}", "alert-danger", false) 34 | else 35 | util.alert(jqxhr.responseText, "alert-danger", false) 36 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | * 13 | * UI Core 14 | *= require jquery-ui/core 15 | *= require jquery-ui/theme 16 | * 17 | *= require_tree . 18 | */ 19 | 20 | input:disabled{ 21 | opacity: 1; 22 | background: #d3d3d3; 23 | }​ 24 | -------------------------------------------------------------------------------- /app/assets/stylesheets/bootstrap_and_overrides.css.less: -------------------------------------------------------------------------------- 1 | @import "1pxdeep/scheme.less"; 2 | @import "twitter/bootstrap/bootstrap"; 3 | @import "1pxdeep/1pxdeep.less"; 4 | 5 | // Set the correct sprite paths 6 | @iconSpritePath: image-url("twitter/bootstrap/glyphicons-halflings.png"); 7 | @iconWhiteSpritePath: image-url("twitter/bootstrap/glyphicons-halflings-white.png"); 8 | 9 | // Set the Font Awesome (Font Awesome is default. You can disable by commenting below lines) 10 | @fontAwesomeEotPath: asset-url("fontawesome-webfont.eot"); 11 | @fontAwesomeEotPath_iefix: asset-url("fontawesome-webfont.eot?#iefix"); 12 | @fontAwesomeWoffPath: asset-url("fontawesome-webfont.woff"); 13 | @fontAwesomeTtfPath: asset-url("fontawesome-webfont.ttf"); 14 | @fontAwesomeSvgPath: asset-url("fontawesome-webfont.svg#fontawesomeregular"); 15 | 16 | // Font Awesome 17 | @import "fontawesome/font-awesome"; 18 | 19 | // Glyphicons 20 | //@import "twitter/bootstrap/sprites.less"; 21 | 22 | // Your custom LESS stylesheets goes here 23 | // 24 | // Since bootstrap was imported above you have access to its mixins which 25 | // you may use and inherit here 26 | // 27 | // If you'd like to override bootstrap's own variables, you can do so here as well 28 | // See http://twitter.github.com/bootstrap/customize.html#variables for their names and documentation 29 | // 30 | // Example: 31 | // @linkColor: #ff0000; 32 | 33 | @input-bg-disabled: @gray-darker; 34 | @seed-color:#f5c9da; 35 | -------------------------------------------------------------------------------- /app/assets/stylesheets/kanbans_edit.css.scss: -------------------------------------------------------------------------------- 1 | li.edit_label i.drag-column { 2 | cursor: move; 3 | } 4 | 5 | li.edit_label input { 6 | color: #333; 7 | } 8 | 9 | ul#hidden_list { 10 | display: none; 11 | } 12 | -------------------------------------------------------------------------------- /app/assets/stylesheets/kanbans_show.css.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the kanbans controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | .label-area { 5 | float: left; 6 | } 7 | 8 | .label-column { 9 | min-height: 800px; 10 | z-index: 10; 11 | } 12 | 13 | .issue-panel { 14 | z-index: 50; 15 | } 16 | 17 | .issue-panel a{ 18 | color: #7e5b68; 19 | } 20 | 21 | .issue-panel .issue-panel__header{ 22 | position: relative; 23 | cursor: move; 24 | } 25 | 26 | .issue-panel .issue-panel__header .issue-panel__assignee{ 27 | position: absolute; 28 | top: 0px; 29 | right: 0px; 30 | } 31 | 32 | .issue-panel .issue-panel__footer{ 33 | position: relative; 34 | } 35 | 36 | .issue-panel .issue-panel__footer .issue-panel__note{ 37 | position: absolute; 38 | top: 10px; 39 | right: 10px; 40 | } 41 | -------------------------------------------------------------------------------- /app/assets/stylesheets/top.css.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the top controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :exception 5 | 6 | helper_method :current_user, :logged_in?, :not_logged_in? 7 | 8 | private 9 | 10 | # if not logged in, redirect to top 11 | def authenticate_user 12 | redirect_to root_path(back_to: request.original_fullpath) if not_logged_in? 13 | end 14 | 15 | def current_user 16 | @current_user ||= User.find_by(id: session[:user_id]) if session[:user_id] 17 | end 18 | 19 | def logged_in? 20 | current_user != nil 21 | end 22 | 23 | def not_logged_in? 24 | !logged_in? 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sue445/gitpeach/08096149c8d768c98159c9061cb324a4b2663be1/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/issues_controller.rb: -------------------------------------------------------------------------------- 1 | class IssuesController < ApplicationController 2 | before_action :set_kanban 3 | before_action :setup_notification, only: [:create, :update] 4 | 5 | unless Rails.env.test? 6 | before_action :authenticate_user 7 | before_action :set_user_kanban 8 | before_action :set_issue 9 | 10 | after_action :notify_event, only: [:create, :update] 11 | 12 | rescue_from StandardError do |exception| 13 | Rails.logger.error exception.full_backtrace 14 | response = { 15 | status: :error, 16 | exception: exception.class.to_s, 17 | message: exception.message 18 | } 19 | render json: response, status: 500 20 | end 21 | end 22 | 23 | # POST /:kanban_id/issues 24 | def create 25 | created_issue = create_gitlab_issue(params[:title]) 26 | render json: created_issue, status: 200 27 | end 28 | 29 | # GET /:kanban_id/issues/:id 30 | def show 31 | render partial: "shared/issue_panel", locals: {issue: @issue} 32 | end 33 | 34 | # PATCH/PUT /:kanban_id/issues/:id 35 | def update 36 | raise ArgumentError, "require to_label_id" unless params[:to_label_id] 37 | 38 | from_label_id = gitlab_current_issue_label_id 39 | 40 | if from_label_id.to_i == params[:to_label_id].to_i 41 | @is_notify_event = false 42 | else 43 | @labels = @kanban.update_gitlab_issue_labels(gitlab_issue_labels, from_label_id, params[:to_label_id]) 44 | @state = @kanban.gitlab_issue_state(from_label_id, params[:to_label_id]) 45 | 46 | update_gitlab_issue(@labels, @state) 47 | end 48 | 49 | render json: updated_issue, status: 200 50 | end 51 | 52 | private 53 | def set_kanban 54 | @kanban = Kanban.friendly.find(params[:kanban_id]) 55 | end 56 | 57 | def set_user_kanban 58 | @user_kanban = UserKanban.new(current_user, @kanban) if current_user 59 | end 60 | 61 | def set_issue 62 | @issue = @user_kanban.issue(params[:id]) 63 | end 64 | 65 | def gitlab_issue_labels 66 | @issue.labels 67 | end 68 | 69 | def gitlab_current_issue_label_id 70 | @kanban.issue_label_id(@issue) 71 | end 72 | 73 | def update_gitlab_issue(labels, state_event) 74 | @user_kanban.update_issue(@issue.id, labels, state_event) 75 | end 76 | 77 | def updated_issue 78 | @user_kanban.issue(params[:id]) 79 | end 80 | 81 | def create_gitlab_issue(title) 82 | @user_kanban.create_issue(title) 83 | end 84 | 85 | def setup_notification 86 | @is_notify_event = true 87 | end 88 | 89 | def notify_event 90 | return unless @is_notify_event 91 | 92 | label_groups = @kanban.label_groups(@user_kanban.issues) 93 | label_group_ids = label_groups.inject({}){|res, label_issues| 94 | label_id = label_issues[:label].id 95 | issues = label_issues[:issues] 96 | res[label_id] = issues.map(&:id) 97 | res 98 | } 99 | 100 | Pusher.trigger("kanban_#{@kanban.id}", :issue_update_event, {label_group_ids: label_group_ids}, {socket_id: params[:socket_id]}) 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /app/controllers/kanbans_controller.rb: -------------------------------------------------------------------------------- 1 | class KanbansController < ApplicationController 2 | before_action :set_kanban, only: [:show, :destroy, :edit, :update, :sync] 3 | 4 | unless Rails.env.test? 5 | before_action :authenticate_user 6 | before_action :set_user_kanban, only: [:show, :sync] 7 | end 8 | 9 | # GET /:id 10 | # GET /:id.json 11 | def show 12 | @label_groups = @kanban.label_groups(project_issues) 13 | end 14 | 15 | # GET /:id/edit 16 | def edit 17 | @labels = @kanban.labels.map{|label| label.attributes.with_indifferent_access } 18 | end 19 | 20 | # POST /:id 21 | # POST /:id.json 22 | def create 23 | @kanban = Kanban.new(kanban_params) 24 | 25 | respond_to do |format| 26 | if @kanban.save 27 | format.html { redirect_to @kanban, notice: 'Kanban was successfully created.' } 28 | format.json { render action: 'show', status: :created, location: @kanban } 29 | else 30 | format.html { render action: 'new' } 31 | format.json { render json: @kanban.errors, status: :unprocessable_entity } 32 | end 33 | end 34 | end 35 | 36 | # PATCH/PUT /:id 37 | # PATCH/PUT /:id.json 38 | def update 39 | is_all_success = true 40 | 41 | Label.transaction do 42 | param_label_ids = params[:labels].inject([]){|array, label_params| array << label_params[:id]; array }.compact.map(&:to_i) 43 | @kanban.labels.each do |label| 44 | label.destroy unless param_label_ids.include?(label.id) 45 | end 46 | 47 | labels_params[:labels].each_with_index do |label_params, index| 48 | # for checked -> unchecked 49 | label_params[:is_backlog_issue] = false unless label_params.has_key?(:is_backlog_issue) 50 | label_params[:is_close_issue] = false unless label_params.has_key?(:is_close_issue) 51 | 52 | label_params[:disp_order] = index 53 | if label_params[:id].blank? 54 | label = @kanban.labels.build(label_params) 55 | is_all_success &= label.save 56 | else 57 | label = @kanban.labels.find(label_params[:id]) 58 | is_all_success &= label.update(label_params.reject{|k,v| k == :id }) 59 | end 60 | 61 | label.errors.each do |attribute, error| 62 | @kanban.errors[attribute] = error 63 | end 64 | end 65 | end 66 | 67 | if is_all_success 68 | redirect_to edit_kanban_path(@kanban), notice: 'Kanban was successfully updated.' 69 | else 70 | @labels = params[:labels] 71 | render action: 'edit' 72 | end 73 | end 74 | 75 | # DELETE /:id 76 | # DELETE /:id.json 77 | def destroy 78 | @kanban.destroy 79 | respond_to do |format| 80 | format.html { redirect_to root_url } 81 | format.json { head :no_content } 82 | end 83 | end 84 | 85 | # GET /:id/sync 86 | def sync 87 | @kanban.gitlab_project_id = @user_kanban.project.id 88 | @kanban.name = @user_kanban.project.path_with_namespace 89 | @kanban.slug = nil 90 | 91 | if @kanban.save 92 | redirect_to edit_kanban_path(@kanban), notice: 'Kanban was synchronized with Gitlab.' 93 | else 94 | redirect_to edit_kanban_path(@kanban), error: 'Failed synchronized with Gitlab.' 95 | end 96 | end 97 | 98 | private 99 | # Use callbacks to share common setup or constraints between actions. 100 | def set_kanban 101 | @kanban = Kanban.friendly.find(params[:id]) 102 | end 103 | 104 | def set_user_kanban 105 | @user_kanban = UserKanban.new(current_user, @kanban) if current_user 106 | end 107 | 108 | # Never trust parameters from the scary internet, only allow the white list through. 109 | def kanban_params 110 | params.require(:kanban).permit(:gitlab_project_id, :name) 111 | end 112 | 113 | def labels_params 114 | params.permit(labels: [:name, :gitlab_label, :is_backlog_issue, :is_close_issue, :disp_order, :id]) 115 | end 116 | 117 | def project_issues 118 | @user_kanban.issues 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /app/controllers/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class SessionsController < ApplicationController 2 | def create 3 | api_response = Gitlab.session(params[:login], params[:password]) 4 | user = User.find_or_create_by(gitlab_user_id: api_response.id) 5 | 6 | user.username = api_response.username 7 | user.private_token = api_response.private_token 8 | user.email = api_response.email 9 | user.save! 10 | 11 | session[:user_id] = user.id 12 | 13 | back_to_path = params[:back_to] ? params[:back_to] : root_path 14 | redirect_to back_to_path, notice: "Signed in!" 15 | 16 | rescue Gitlab::Error::Unauthorized => e 17 | redirect_to root_path(back_to: params[:back_to]), alert: "Unauthorized" 18 | end 19 | 20 | def destroy 21 | session[:user_id] = nil 22 | redirect_to root_path, notice: "Signed Out!" 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/controllers/top_controller.rb: -------------------------------------------------------------------------------- 1 | class TopController < ApplicationController 2 | def index 3 | if current_user 4 | projects = current_user.projects 5 | @user_kanban_projects = projects.select{|project| Kanban.exists?(gitlab_project_id: project.id) } 6 | @no_kanban_projects = projects.reject{|project| Kanban.exists?(gitlab_project_id: project.id) } 7 | 8 | user_project_ids = @user_kanban_projects.map(&:id) 9 | @other_kanbans = Kanban.where.not(gitlab_project_id: user_project_ids) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/issues_helper.rb: -------------------------------------------------------------------------------- 1 | module IssuesHelper 2 | # via. 3 | # * https://github.com/gitlabhq/gitlabhq/blob/master/lib/gitlab/issues_labels.rb 4 | # * https://github.com/gitlabhq/gitlabhq/blob/master/app/helpers/labels_helper.rb 5 | def label_css_class(name) 6 | warning_labels = %w(documentation support) 7 | neutral_labels = %w(discussion suggestion) 8 | positive_labels = %w(feature enhancement) 9 | important_labels = %w(bug critical confirmed) 10 | 11 | case name 12 | when *warning_labels 13 | 'label-warning' 14 | when *neutral_labels 15 | 'label-primary' 16 | when *positive_labels 17 | 'label-success' 18 | when *important_labels 19 | 'label-danger' 20 | else 21 | 'label-info' 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/helpers/kanbans_helper.rb: -------------------------------------------------------------------------------- 1 | module KanbansHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/people_helper.rb: -------------------------------------------------------------------------------- 1 | require 'avatar/view/action_view_support' 2 | 3 | module PeopleHelper 4 | include Avatar::View::ActionViewSupport 5 | end 6 | -------------------------------------------------------------------------------- /app/helpers/top_helper.rb: -------------------------------------------------------------------------------- 1 | module TopHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sue445/gitpeach/08096149c8d768c98159c9061cb324a4b2663be1/app/mailers/.keep -------------------------------------------------------------------------------- /app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sue445/gitpeach/08096149c8d768c98159c9061cb324a4b2663be1/app/models/.keep -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sue445/gitpeach/08096149c8d768c98159c9061cb324a4b2663be1/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/kanban.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: kanbans 4 | # 5 | # id :integer not null, primary key 6 | # gitlab_project_id :integer 7 | # name :string(255) 8 | # slug :string(255) 9 | # created_at :datetime 10 | # updated_at :datetime 11 | # 12 | # Indexes 13 | # 14 | # index_kanbans_on_name (name) UNIQUE 15 | # index_kanbans_on_slug (slug) UNIQUE 16 | # 17 | 18 | class Kanban < ActiveRecord::Base 19 | extend FriendlyId 20 | friendly_id :name, use: :slugged 21 | 22 | has_many :labels, -> { order(:disp_order) }, dependent: :destroy 23 | 24 | after_create :create_default_labels 25 | 26 | def normalize_friendly_id(text) 27 | text 28 | end 29 | 30 | # @return [Hash] key: label_id, value: issues 31 | def issues_group_by_label(issues) 32 | issues ||= [] 33 | issues.group_by{|issue| issue_label_id(issue) } 34 | end 35 | 36 | def label_groups(issues) 37 | issues_group_by_label = self.issues_group_by_label(issues) 38 | done_label = self.labels.done.first 39 | 40 | label_groups = [] 41 | self.labels.each do |label| 42 | issues = issues_group_by_label[label.id] || [] 43 | if label == done_label 44 | # reject old closed tasks 45 | issues = issues.reject{|issue| issue.updated_at < 1.week.ago } 46 | end 47 | 48 | label_groups << { 49 | label: label, 50 | issues: issues 51 | } 52 | end 53 | 54 | label_groups 55 | end 56 | 57 | # @return [Integer] label_id 58 | def issue_label_id(issue) 59 | if issue.state == "closed" 60 | self.labels.done.first.id 61 | else 62 | not_backlog_label = self.labels.other.where(gitlab_label: issue.labels).first 63 | not_backlog_label ? not_backlog_label.id : self.labels.backlog.first.id 64 | end 65 | end 66 | 67 | # @param gitlab_labels [Array] 68 | # @param from_label_id [Integer] 69 | # @param to_label_id [Integer] 70 | # @return [Array] 71 | def update_gitlab_issue_labels(gitlab_labels, from_label_id, to_label_id) 72 | from_label = self.labels.find(from_label_id) 73 | to_label = self.labels.find(to_label_id) 74 | 75 | updated_labels = [] 76 | updated_labels << to_label.gitlab_label if from_label.is_backlog_issue? || from_label.is_close_issue? 77 | 78 | gitlab_labels.each do |gitlab_label| 79 | if gitlab_label == from_label.gitlab_label 80 | updated_labels << to_label.gitlab_label 81 | else 82 | updated_labels << gitlab_label 83 | end 84 | end 85 | 86 | updated_labels.compact 87 | end 88 | 89 | def gitlab_issue_state(from_label_id, to_label_id) 90 | from_label = self.labels.find(from_label_id) 91 | to_label = self.labels.find(to_label_id) 92 | 93 | if from_label.opened? && to_label.closed? 94 | "close" 95 | elsif from_label.closed? && to_label.opened? 96 | "reopen" 97 | else 98 | # send "open" to "opened" issue (or send "close" to "closed" issue), 404 error at gitlab v6.4.3 99 | nil 100 | end 101 | end 102 | 103 | private 104 | def create_default_labels 105 | Label::DEFAULTS.each_with_index do |params, index| 106 | labels.create(params.merge(disp_order: index)) 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /app/models/label.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: labels 4 | # 5 | # id :integer not null, primary key 6 | # kanban_id :integer 7 | # name :string(255) 8 | # gitlab_label :string(255) 9 | # disp_order :integer 10 | # is_backlog_issue :boolean 11 | # is_close_issue :boolean 12 | # created_at :datetime 13 | # updated_at :datetime 14 | # 15 | # Indexes 16 | # 17 | # index_labels_on_kanban_id_and_disp_order (kanban_id,disp_order) 18 | # 19 | 20 | class Label < ActiveRecord::Base 21 | DEFAULTS = [ 22 | {name: "Backlog" , gitlab_label: nil , is_backlog_issue: true , is_close_issue: false}, 23 | {name: "Ready" , gitlab_label: "ready" , is_backlog_issue: false, is_close_issue: false}, 24 | {name: "In Progress", gitlab_label: "in progress", is_backlog_issue: false, is_close_issue: false}, 25 | {name: "Done" , gitlab_label: nil , is_backlog_issue: false, is_close_issue: true}, 26 | ] 27 | 28 | scope :backlog, -> { where(is_backlog_issue: true , is_close_issue: false) } 29 | scope :done , -> { where(is_backlog_issue: false, is_close_issue: true) } 30 | scope :other , -> { where(is_backlog_issue: false, is_close_issue: false) } 31 | 32 | validates_presence_of :name 33 | validates_presence_of :gitlab_label, if: -> label{ !label.is_backlog_issue? && !label.is_close_issue? } 34 | 35 | before_save :normalize_gitlab_label 36 | 37 | def closed? 38 | self.is_close_issue? 39 | end 40 | 41 | def opened? 42 | !self.closed? 43 | end 44 | 45 | private 46 | def normalize_gitlab_label 47 | self.gitlab_label = nil if self.is_backlog_issue? || self.is_close_issue? 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: users 4 | # 5 | # id :integer not null, primary key 6 | # gitlab_user_id :integer 7 | # username :string(255) 8 | # email :string(255) 9 | # private_token :string(255) 10 | # created_at :datetime 11 | # updated_at :datetime 12 | # 13 | # Indexes 14 | # 15 | # index_users_on_gitlab_user_id (gitlab_user_id) UNIQUE 16 | # 17 | 18 | class User < ActiveRecord::Base 19 | def projects 20 | gitlab.projects(page: 1, per_page: 100) 21 | end 22 | 23 | def gitlab 24 | Gitlab.client(private_token: self.private_token) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/models/user_kanban.rb: -------------------------------------------------------------------------------- 1 | class UserKanban 2 | attr_reader :project 3 | 4 | def initialize(user, kanban) 5 | @user = user 6 | @kanban = kanban 7 | @project = user.gitlab.project(@kanban.gitlab_project_id) 8 | end 9 | 10 | def issues 11 | # sort by newest 12 | @user.gitlab.issues(@kanban.gitlab_project_id, page: 1, per_page: 100).sort{ |a,b| b.updated_at <=> a.updated_at } 13 | end 14 | 15 | def issue(issue_id) 16 | @user.gitlab.issue(@kanban.gitlab_project_id, issue_id) 17 | end 18 | 19 | def issue_url(issue) 20 | issue_id = issue.iid || issue.id 21 | "#{@project.web_url}/issues/#{issue_id}" 22 | end 23 | 24 | def update_issue(issue_id, labels, state_event) 25 | options = { 26 | labels: labels.empty? ? %q{''} : labels.join(",") 27 | } 28 | options[:state_event] = state_event if state_event 29 | @user.gitlab.edit_issue(@project.id, issue_id, options) 30 | end 31 | 32 | def create_issue(title) 33 | @user.gitlab.create_issue(@project.id, title) 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /app/views/kanbans/edit.html.slim: -------------------------------------------------------------------------------- 1 | h1 2 | | Editing kanban 3 | 4 | = link_to sync_kanban_path, class: "btn btn-default btn-xs help-tooltip", "data-toggle"=>"tooltip", title: "Sync with Gitlab (if changed repository name, click this)" do 5 | span.glyphicon.glyphicon-refresh 6 | 7 | = button_tag :add_label, type: "button", class: "btn btn-default btn-xs", id: :add_label do 8 | span.glyphicon.glyphicon-plus 9 | 10 | - if @kanban.errors.any? 11 | #error_explanation.alert.alert-warning 12 | h4 = "#{pluralize(@kanban.errors.count, "error")} prohibited this kanban from being saved:" 13 | ul 14 | - @kanban.errors.full_messages.each do |message| 15 | li = message 16 | 17 | = form_tag kanban_path, id: "update_kanban", method: :put do 18 | ul.list-group 19 | - @labels.each do |label| 20 | li.edit_label.list-group-item 21 | i.glyphicon.glyphicon-align-justify.drag-column 22 | = hidden_field_tag "labels[][id]", label[:id] 23 | = text_field_tag "labels[][name]", label[:name], id: nil, placeholder: "name" 24 | = text_field_tag "labels[][gitlab_label]", label[:gitlab_label], id: nil, placeholder: "gitlab label" 25 | 26 | label.btn.btn-success 27 | = radio_button_tag "labels[][is_backlog_issue]", true, label[:is_backlog_issue], id: nil 28 | | Backlog Issue 29 | = link_to "#", "data-toggle"=>"tooltip", title: "Check to display all open issues in this column", class: "help-tooltip" do 30 | span.glyphicon.glyphicon-question-sign 31 | 32 | label.btn.btn-warning 33 | = radio_button_tag "labels[][is_close_issue]", true, label[:is_close_issue], id: nil 34 | | Close Issue 35 | = link_to "#", "data-toggle"=>"tooltip", title: "Check to close an issue on Gitlab when a card is dragged into this column", class: "help-tooltip" do 36 | span.glyphicon.glyphicon-question-sign 37 | 38 | = button_tag :delete_label, type: "button", class: "btn btn-danger delete_label", id: nil do 39 | span.glyphicon.glyphicon-trash 40 | 41 | div 42 | = button_tag :submit_button, class: "btn btn-primary" do 43 | span.glyphicon.glyphicon-floppy-save 44 | | Save 45 | 46 | = link_to kanban_path, class: "btn btn-default" do 47 | | Back 48 | 49 | 50 | = javascript_include_tag "kanbans_edit" 51 | 52 | 53 | / if append line, copied DOM 54 | ul#hidden_list 55 | li.edit_label.list-group-item 56 | i.glyphicon.glyphicon-align-justify.drag-column 57 | = hidden_field_tag "labels[][id]", nil 58 | = text_field_tag "labels[][name]", nil, id: nil, placeholder: "name" 59 | = text_field_tag "labels[][gitlab_label]", nil, id: nil, placeholder: "gitlab label" 60 | 61 | label.btn.btn-success 62 | = radio_button_tag "labels[][is_backlog_issue]", true, nil, id: nil 63 | | Backlog Issue 64 | = link_to "#", "data-toggle"=>"tooltip", title: "Check to display all open issues in this column", class: "help-tooltip" do 65 | span.glyphicon.glyphicon-question-sign 66 | 67 | label.btn.btn-warning 68 | = radio_button_tag "labels[][is_close_issue]", true, nil, id: nil 69 | | Close Issue 70 | = link_to "#", "data-toggle"=>"tooltip", title: "Check to close an issue on Gitlab when a card is dragged into this column", class: "help-tooltip" do 71 | span.glyphicon.glyphicon-question-sign 72 | 73 | = button_tag :delete_label, type: "button", class: "btn btn-danger delete_label", id: nil do 74 | span.glyphicon.glyphicon-trash 75 | -------------------------------------------------------------------------------- /app/views/kanbans/show.html.slim: -------------------------------------------------------------------------------- 1 | - content_for :title, @kanban.name 2 | 3 | 4 | h1 5 | = link_to @kanban.name, @user_kanban.project.web_url 6 | = link_to edit_kanban_path, class: "btn btn-default btn-xs" do 7 | span.glyphicon.glyphicon-wrench 8 | 9 | = hidden_field_tag :label_groups_count, @label_groups.count 10 | 11 | form.form-inline#create_issue_form role="form" 12 | .form-group 13 | = text_field_tag :new_issue_title, nil, class: "form-control", placeholder: "issue title", size: 50 14 | button.btn.btn-primary#create_issue_button type="submit" 15 | span.glyphicon.glyphicon-plus 16 | | New Issue 17 | 18 | = image_tag "loading.gif", id: "loading", style: "display: none" 19 | 20 | .row 21 | - @label_groups.each do |label_group| 22 | - label = label_group[:label] 23 | - issues = label_group[:issues] 24 | .label-area 25 | h3 26 | - if label.is_backlog_issue? 27 | span.glyphicon.glyphicon-tasks 28 | - if label.is_close_issue? 29 | span.glyphicon.glyphicon-ok 30 | 31 | = label.name 32 | span.badge id="count_#{label.id}" 33 | = issues.count 34 | 35 | div.well.label-column id="label_#{label.id}" 36 | = render partial: "shared/issue_panel", collection: issues, as: :issue 37 | 38 | = hidden_field_tag :kanban_name, @kanban.name 39 | = hidden_field_tag :pusher_key , Pusher.key 40 | = hidden_field_tag :channel , "kanban_#{@kanban.id}" 41 | 42 | = javascript_include_tag "http://js.pusher.com/2.1/pusher.min.js" 43 | 44 | - unless Rails.env.production? 45 | coffee: 46 | window.Pusher.log = (message) -> 47 | window.console.log(message) if window.console && window.console.log 48 | 49 | = javascript_include_tag "kanbans_show" 50 | -------------------------------------------------------------------------------- /app/views/kanbans/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.kanban @kanban, :id, :gitlab_project_id, :name, :slug, :created_at, :updated_at 2 | json.label_groups @label_groups, :label, :issues 3 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.slim: -------------------------------------------------------------------------------- 1 | doctype html 2 | html lang="en" 3 | head 4 | meta charset="utf-8" 5 | meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" 6 | meta name="viewport" content="width=device-width, initial-scale=1.0" 7 | title= content_for?(:title) ? yield(:title) + " | Gitpeach" : "Gitpeach" 8 | = csrf_meta_tags 9 | 10 | /! Le HTML5 shim, for IE6-8 support of HTML elements 11 | /[if lt IE 9] 12 | = javascript_include_tag "//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.6.1/html5shiv.js" 13 | = stylesheet_link_tag "application", :media => "all" 14 | = favicon_link_tag 'apple-touch-icon-144x144-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png', :sizes => '144x144' 15 | = favicon_link_tag 'apple-touch-icon-114x114-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png', :sizes => '114x114' 16 | = favicon_link_tag 'apple-touch-icon-72x72-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png', :sizes => '72x72' 17 | = favicon_link_tag 'apple-touch-icon-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png' 18 | = favicon_link_tag 'favicon.ico', :rel => 'shortcut icon' 19 | = javascript_include_tag "application" 20 | 21 | 22 | 23 | body 24 | - if logged_in? 25 | .navbar.navbar-static-top 26 | .container 27 | button.navbar-toggle type="button" data-toggle="collapse" data-target=".navbar-responsive-collapse" 28 | span.icon-bar 29 | span.icon-bar 30 | span.icon-bar 31 | a.navbar-brand href="/" 32 | | Gitpeach 33 | .nav-collapse.collapse.in.navbar-responsive-collapse 34 | ul.nav.navbar-nav.pull-right 35 | li 36 | = link_to logout_path, alt: "logout", title: "logout" do 37 | span.glyphicon.glyphicon-log-out 38 | li 39 | = link_to "#" do 40 | = avatar_tag(current_user, {size: 24}, class: "img-rounded") 41 | 42 | .container 43 | #alert-area 44 | .row 45 | .col-lg-12 46 | = bootstrap_flash 47 | = yield 48 | 49 | footer 50 | p © sue445 2014- 51 | -------------------------------------------------------------------------------- /app/views/shared/_issue_panel.html.slim: -------------------------------------------------------------------------------- 1 | .panel.panel-primary.issue-panel id="issue_#{issue.id}" 2 | .panel-heading.issue-panel__header 3 | h3.panel-title 4 | = issue.iid || issue.iid 5 | - if issue.assignee 6 | = avatar_tag(issue.assignee, {size: 36}, class: "img-circle issue-panel__assignee") 7 | .panel-body 8 | .issue-panel__title 9 | = link_to @user_kanban.issue_url(issue) do 10 | = issue.title 11 | small.issue-panel__updated_at data-toggle="tooltip" title=Time.zone.parse(issue.updated_at) 12 | span.glyphicon.glyphicon-time 13 | = " #{time_ago_in_words(issue.updated_at, include_seconds: true)} ago" 14 | .panel-footer.issue-panel__footer 15 | .issue-panel__labels 16 | - issue.labels.each do |label| 17 | span.label class=label_css_class(label) 18 | = label 19 | - if issue.milestone 20 | .issue-panel__milestone 21 | p.text-success 22 | span.glyphicon.glyphicon-calendar 23 | = issue.milestone.title 24 | .issue-panel__note 25 | = link_to "#{@user_kanban.issue_url(issue)}#notes" do 26 | span.glyphicon.glyphicon-comment 27 | -------------------------------------------------------------------------------- /app/views/top/_logged_in.html.slim: -------------------------------------------------------------------------------- 1 | - unless @user_kanban_projects.empty? 2 | h1 Your Kanbans 3 | 4 | table.table 5 | tbody 6 | - @user_kanban_projects.each do |project| 7 | tr 8 | td 9 | = link_to kanban_path(project.path_with_namespace) do 10 | = project.path_with_namespace 11 | td 12 | = link_to kanban_path(project.path_with_namespace), class: "btn btn-default" do 13 | span.glyphicon.glyphicon-tasks 14 | | Show 15 | = link_to edit_kanban_path(project.path_with_namespace), class: "btn btn-default" do 16 | span.glyphicon.glyphicon-wrench 17 | | Edit 18 | = link_to project.path_with_namespace, data: {:confirm => 'Are you sure?'}, class: "btn btn-danger", method: :delete do 19 | span.glyphicon.glyphicon-trash 20 | | Destroy 21 | 22 | - unless @other_kanbans.empty? 23 | h1 Other Kanbans 24 | 25 | table.table 26 | tbody 27 | - @other_kanbans.each do |kanban| 28 | tr 29 | td 30 | = link_to kanban_path(kanban) do 31 | = kanban.name 32 | td 33 | = link_to kanban_path(kanban), class: "btn btn-default" do 34 | span.glyphicon.glyphicon-tasks 35 | | Show 36 | 37 | 38 | - unless @no_kanban_projects.empty? 39 | h1 Your Projects 40 | 41 | table.table 42 | tbody 43 | - @no_kanban_projects.each do |project| 44 | tr 45 | td 46 | = link_to project.path_with_namespace, project.web_url 47 | td 48 | = form_tag kanbans_path 49 | = hidden_field_tag "kanban[gitlab_project_id]", project.id 50 | = hidden_field_tag "kanban[name]", project.path_with_namespace 51 | = button_tag type: "submit", class: "btn btn-primary" do 52 | span.glyphicon.glyphicon-file 53 | | Create Kanban 54 | -------------------------------------------------------------------------------- /app/views/top/_not_logged_in.html.slim: -------------------------------------------------------------------------------- 1 | = link_to "https://github.com/sue445/gitpeach" do 2 | = image_tag "https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png", alt: "Fork me on GitHub", style: "position: absolute; top: 0; right: 0; border: 0;" 3 | 4 | h1 Gitpeach 5 | 6 | - gitlab_url = Gitlab.endpoint.gsub(%r{/api/.+}, "/") 7 | 8 | .alert.alert-info 9 | | Input username/password of 10 | |   11 | = link_to(gitlab_url, gitlab_url) 12 | 13 | = form_tag login_path, class: "form-horizontal" do 14 | = hidden_field_tag :back_to, params[:back_to] 15 | .form-group 16 | label for="login" class="col-sm-2 control-label" 17 | | Login 18 | .col-sm-8 19 | = text_field_tag :login, nil, class: "form-control" 20 | .form-group 21 | label for="password" class="col-sm-2 control-label" 22 | | Password 23 | .col-sm-8 24 | = password_field_tag :password, nil, class: "form-control" 25 | .form-group 26 | .col-sm-offset-2.col-sm-10 27 | = button_tag type: "submit", class: "btn btn-default" do 28 | | Login with Gitlab 29 | 30 | coffee: 31 | $(document).ready -> 32 | $("#login").focus() -------------------------------------------------------------------------------- /app/views/top/index.html.slim: -------------------------------------------------------------------------------- 1 | - if logged_in? 2 | = render partial: "logged_in" 3 | - else 4 | = render partial: "not_logged_in" 5 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../../config/application', __FILE__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /bin/unicorn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.dirname(__FILE__) + '/../config/boot' 3 | 4 | require "fileutils" 5 | require "optparse" 6 | 7 | RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) 8 | options = { 9 | "-c" => File.join(RAILS_ROOT, 'config', 'unicorn.rb'), 10 | "-E" => "development", 11 | "-p" => "8080" 12 | } 13 | 14 | ARGV.clone.options do |opts| 15 | opts.on("-d", "--daemon", "Make server run as a Daemon") { options["-D"] = nil } 16 | opts.on("-e", "--env=environment", "Rails environment(default: development)") {|v| options["-E"] = v } 17 | opts.on("-c", "--config=file", "use custom unicorn configuration file(default: config/unicorn.rb)") {|v| options["-c"] = v } 18 | opts.on("-u", "--debugger", "Enable Debugger") {|v| options["-d"] = nil } 19 | opts.on("-p", "--port=number", "Port number") {|v| options["-p"] = v} 20 | 21 | opts.on("-h", "--help", "Show this help message.") { puts opts; exit } 22 | opts.parse! 23 | end 24 | 25 | 26 | class UnicornManager 27 | class << self 28 | def start(options = {}) 29 | exec("unicorn_rails #{options.to_a.join(' ')}") 30 | end 31 | 32 | # reloading config but deployed application code cannot be loaded when preload_app == true. 33 | def hup(options = {}) 34 | if send_signal("HUP", master_pid) 35 | puts "unicorn master successfully SIGHUPed." 36 | else 37 | puts "cannot send SIGHUP signal to unicorn server." 38 | end 39 | end 40 | 41 | # graceful_restarting with USR2+QUIT 42 | def graceful(options = {}) 43 | if send_signal("USR2", master_pid) 44 | puts "send USR2 to unicorn master successfully.." 45 | else 46 | puts "cannot send USR2 signal to unicorn server." 47 | end 48 | end 49 | 50 | def stop(options = {}) 51 | if send_signal("QUIT", master_pid) 52 | puts "unicorn master successfully SIGQUITed" 53 | else 54 | puts "cannot send SIGQUIT signal to unicorn server." 55 | end 56 | end 57 | 58 | def kill(options = {}) 59 | if send_signal("INT", master_pid) 60 | puts "unicorn master successfully SIGINTed" 61 | else 62 | puts "cannot send SIGINT signal to unicorn server." 63 | end 64 | end 65 | 66 | def rotatelog(options = {}) 67 | if send_signal("USR1", master_pid) 68 | puts "unicorn master successfully SIGUSR1ed" 69 | else 70 | puts "cannot send SIGUSR1 signal to unicorn server." 71 | end 72 | end 73 | 74 | def incr(options = {}) 75 | if send_signal("TTIN", master_pid) 76 | puts "unicorn master successfully SIGTTINed" 77 | else 78 | puts "cannot send SIGTTIN signal to unicorn server." 79 | end 80 | end 81 | 82 | def decr(options = {}) 83 | if send_signal("TTOU", master_pid) 84 | puts "unicorn master successfully SIGTTOUed" 85 | else 86 | puts "cannot send SIGTTOU signal to unicorn server." 87 | end 88 | end 89 | 90 | def command(command, options) 91 | self.send(command, options) 92 | end 93 | 94 | def master_pid 95 | File.read(File.join(RAILS_ROOT, 'tmp', 'pids', 'unicorn.pid')).strip 96 | end 97 | 98 | def old_master_pid 99 | File.read(File.join(RAILS_ROOT, 'tmp', 'pids', 'unicorn.pid.oldbin')).strip 100 | end 101 | 102 | def send_signal(signal, pid) 103 | exec("kill", "-#{signal}", pid) 104 | end 105 | end 106 | end 107 | 108 | command = if ["start", "stop", "hup", "graceful", "kill", "rotatelog", "incr", "decr"].include?(ARGV.first) 109 | ARGV.first 110 | else 111 | "start" 112 | end 113 | 114 | if options["-E"] != "development" 115 | options.delete("-p") 116 | end 117 | UnicornManager.command(command, options) 118 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module Gitpeach 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified here. 12 | # Application configuration should go into files in config/initializers 13 | # -- all .rb files in that directory are automatically loaded. 14 | 15 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 16 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 17 | # config.time_zone = 'Central Time (US & Canada)' 18 | 19 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 20 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 21 | # config.i18n.default_locale = :de 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 5 | -------------------------------------------------------------------------------- /config/database.yml.mysql: -------------------------------------------------------------------------------- 1 | # MySQL. Versions 4.1 and 5.0 are recommended. 2 | # 3 | # Install the MYSQL driver 4 | # gem install mysql2 5 | # 6 | # Ensure the MySQL gem is defined in your Gemfile 7 | # gem 'mysql2' 8 | # 9 | # And be sure to use new-style password hashing: 10 | # http://dev.mysql.com/doc/refman/5.0/en/old-client.html 11 | development: 12 | adapter: mysql2 13 | encoding: utf8 14 | database: gitpeach_development 15 | pool: 5 16 | username: root 17 | # password: root 18 | # socket: /tmp/mysql.sock 19 | 20 | # Warning: The database defined as "test" will be erased and 21 | # re-generated from your development database when you run "rake". 22 | # Do not set this db to the same as development or production. 23 | test: 24 | adapter: mysql2 25 | encoding: utf8 26 | database: gitpeach_test 27 | pool: 5 28 | username: root 29 | # password: root 30 | # socket: /tmp/mysql.sock 31 | 32 | production: 33 | adapter: mysql2 34 | encoding: utf8 35 | database: gitpeach_production 36 | pool: 5 37 | username: momozono 38 | password: love 39 | socket: /tmp/mysql.sock 40 | -------------------------------------------------------------------------------- /config/database.yml.postgresql: -------------------------------------------------------------------------------- 1 | # PostgreSQL. Versions 8.2 and up are supported. 2 | # 3 | # Install the pg driver: 4 | # gem install pg 5 | # On OS X with Homebrew: 6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config 7 | # On OS X with MacPorts: 8 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config 9 | # On Windows: 10 | # gem install pg 11 | # Choose the win32 build. 12 | # Install PostgreSQL and put its /bin directory on your path. 13 | # 14 | # Configure Using Gemfile 15 | # gem 'pg' 16 | # 17 | development: 18 | adapter: postgresql 19 | encoding: unicode 20 | database: gitpeach_development 21 | pool: 5 22 | username: root 23 | # password: root 24 | 25 | # Connect on a TCP socket. Omitted by default since the client uses a 26 | # domain socket that doesn't need configuration. Windows does not have 27 | # domain sockets, so uncomment these lines. 28 | #host: localhost 29 | 30 | # The TCP port the server listens on. Defaults to 5432. 31 | # If your server runs on a different port number, change accordingly. 32 | #port: 5432 33 | 34 | # Schema search path. The server defaults to $user,public 35 | #schema_search_path: myapp,sharedapp,public 36 | 37 | # Minimum log levels, in increasing order: 38 | # debug5, debug4, debug3, debug2, debug1, 39 | # log, notice, warning, error, fatal, and panic 40 | # Defaults to warning. 41 | #min_messages: notice 42 | 43 | # Warning: The database defined as "test" will be erased and 44 | # re-generated from your development database when you run "rake". 45 | # Do not set this db to the same as development or production. 46 | test: 47 | adapter: postgresql 48 | encoding: unicode 49 | database: gitpeach_test 50 | pool: 5 51 | # username: root 52 | # password: root 53 | 54 | production: 55 | adapter: postgresql 56 | encoding: unicode 57 | database: gitpeach_production 58 | pool: 5 59 | username: momozono 60 | password: love 61 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations. 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | 30 | # Adds additional error checking when serving assets at runtime. 31 | # Checks for improperly declared sprockets dependencies. 32 | # Raises helpful error messages. 33 | config.assets.raise_runtime_errors = true 34 | 35 | # Raises error for missing translations 36 | # config.action_view.raise_on_missing_translations = true 37 | 38 | config.assets.precompile += %w( *.js ) 39 | end 40 | 41 | # indent slim view 42 | Slim::Engine.set_default_options pretty: true 43 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. 20 | # config.action_dispatch.rack_cache = true 21 | 22 | # Disable Rails's static asset server (Apache or nginx will already do this). 23 | config.serve_static_assets = false 24 | 25 | # Compress JavaScripts and CSS. 26 | config.assets.js_compressor = :uglifier 27 | # config.assets.css_compressor = :sass 28 | 29 | # Do not fallback to assets pipeline if a precompiled asset is missed. 30 | config.assets.compile = false 31 | 32 | # Generate digests for assets URLs. 33 | config.assets.digest = true 34 | 35 | # Version of your assets, change this if you want to expire all your assets. 36 | config.assets.version = '1.0' 37 | 38 | # Specifies the header that your server uses for sending files. 39 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 40 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 41 | 42 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 43 | # config.force_ssl = true 44 | 45 | # Set to :debug to see everything in the log. 46 | config.log_level = :info 47 | 48 | # Prepend all log lines with the following tags. 49 | # config.log_tags = [ :subdomain, :uuid ] 50 | 51 | # Use a different logger for distributed setups. 52 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 53 | 54 | # Use a different cache store in production. 55 | # config.cache_store = :mem_cache_store 56 | 57 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 58 | # config.action_controller.asset_host = "http://assets.example.com" 59 | 60 | # Precompile additional assets. 61 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 62 | # config.assets.precompile += %w( search.js ) 63 | config.assets.precompile += %w( *.js ) 64 | 65 | # Ignore bad email addresses and do not raise email delivery errors. 66 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 67 | # config.action_mailer.raise_delivery_errors = false 68 | 69 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 70 | # the I18n.default_locale when a translation cannot be found). 71 | config.i18n.fallbacks = true 72 | 73 | # Send deprecation notices to registered listeners. 74 | config.active_support.deprecation = :notify 75 | 76 | # Disable automatic flushing of the log to improve performance. 77 | # config.autoflush_log = false 78 | 79 | # Use default logging formatter so that PID and timestamp are not suppressed. 80 | config.log_formatter = ::Logger::Formatter.new 81 | 82 | # Do not dump schema after migrations. 83 | config.active_record.dump_schema_after_migration = false 84 | end 85 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static asset server for tests with Cache-Control for performance. 16 | config.serve_static_assets = true 17 | config.static_cache_control = 'public, max-age=3600' 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Print deprecation notices to the stderr. 35 | config.active_support.deprecation = :stderr 36 | 37 | # Raises error for missing translations 38 | # config.action_view.raise_on_missing_translations = true 39 | end 40 | -------------------------------------------------------------------------------- /config/gitlab.yml.sample: -------------------------------------------------------------------------------- 1 | development: &development 2 | endpoint: https://example.net/api/v3 3 | 4 | test: 5 | <<: *development 6 | 7 | production: 8 | <<: *development 9 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :hybrid 4 | -------------------------------------------------------------------------------- /config/initializers/exception_full_backtrace.rb: -------------------------------------------------------------------------------- 1 | class Exception 2 | def full_backtrace 3 | ([self.to_s] + self.backtrace).join("\n") 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /config/initializers/friendly_id.rb: -------------------------------------------------------------------------------- 1 | # FriendlyId Global Configuration 2 | # 3 | # Use this to set up shared configuration options for your entire application. 4 | # Any of the configuration options shown here can also be applied to single 5 | # models by passing arguments to the `friendly_id` class method or defining 6 | # methods in your model. 7 | # 8 | # To learn more, check out the guide: 9 | # 10 | # http://norman.github.io/friendly_id/file.Guide.html 11 | 12 | FriendlyId.defaults do |config| 13 | # ## Reserved Words 14 | # 15 | # Some words could conflict with Rails's routes when used as slugs, or are 16 | # undesirable to allow as slugs. Edit this list as needed for your app. 17 | config.use :reserved 18 | 19 | config.reserved_words = %w(new edit index session login logout users admin 20 | stylesheets assets javascripts images) 21 | 22 | # ## Friendly Finders 23 | # 24 | # Uncomment this to use friendly finders in all models. By default, if 25 | # you wish to find a record by its friendly id, you must do: 26 | # 27 | # MyModel.friendly.find('foo') 28 | # 29 | # If you uncomment this, you can do: 30 | # 31 | # MyModel.find('foo') 32 | # 33 | # This is significantly more convenient but may not be appropriate for 34 | # all applications, so you must explicity opt-in to this behavior. You can 35 | # always also configure it on a per-model basis if you prefer. 36 | # 37 | # Something else to consider is that using the :finders addon boosts 38 | # performance because it will avoid Rails-internal code that makes runtime 39 | # calls to `Module.extend`. 40 | # 41 | # config.use :finders 42 | # 43 | # ## Slugs 44 | # 45 | # Most applications will use the :slugged module everywhere. If you wish 46 | # to do so, uncomment the following line. 47 | # 48 | # config.use :slugged 49 | # 50 | # By default, FriendlyId's :slugged addon expects the slug column to be named 51 | # 'slug', but you can change it if you wish. 52 | # 53 | # config.slug_column = 'slug' 54 | # 55 | # When FriendlyId can not generate a unique ID from your base method, it appends 56 | # a UUID, separated by a single dash. You can configure the character used as the 57 | # separator. If you're upgrading from FriendlyId 4, you may wish to replace this 58 | # with two dashes. 59 | # 60 | # config.sequence_separator = '-' 61 | # 62 | # ## Tips and Tricks 63 | # 64 | # ### Controlling when slugs are generated 65 | # 66 | # As of FriendlyId 5.0, new slugs are generated only when the slug field is 67 | # nil, but you if you're using a column as your base method can change this 68 | # behavior by overriding the `should_generate_new_friendly_id` method that 69 | # FriendlyId adds to your model. The change below makes FriendlyId 5.0 behave 70 | # more like 4.0. 71 | # 72 | # config.use Module.new { 73 | # def should_generate_new_friendly_id? 74 | # slug.blank? || _changed? 75 | # end 76 | # } 77 | # 78 | # FriendlyId uses Rails's `parameterize` method to generate slugs, but for 79 | # languages that don't use the Roman alphabet, that's not usually suffient. Here 80 | # we use the Babosa library to transliterate Russian Cyrillic slugs to ASCII. If 81 | # you use this, don't forget to add "babosa" to your Gemfile. 82 | # 83 | # config.use Module.new { 84 | # def normalize_friendly_id(text) 85 | # text.to_slug.normalize! :transliterations => [:russian, :latin] 86 | # end 87 | # } 88 | end 89 | -------------------------------------------------------------------------------- /config/initializers/gitlab.rb: -------------------------------------------------------------------------------- 1 | Gitlab.configure do |config| 2 | gitlab_config = YAML.load_file(File.join(Rails.root, "config", "gitlab.yml")).with_indifferent_access 3 | config.endpoint = gitlab_config[Rails.env][:endpoint] # API endpoint URL (required) 4 | config.private_token = "dummy" 5 | #config.user_agent = 'Custom User Agent' # user agent, default to 'Gitlab Ruby Gem [version]' (optional) 6 | end 7 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /config/initializers/pusher.rb: -------------------------------------------------------------------------------- 1 | require 'pusher' 2 | 3 | config_file = File.join(Rails.root, "config", "pusher.yml") 4 | if File.exists?(config_file) 5 | pusher_config = YAML.load_file(config_file).with_indifferent_access 6 | Pusher.app_id = pusher_config[Rails.env][:app_id] 7 | Pusher.key = pusher_config[Rails.env][:key] 8 | Pusher.secret = pusher_config[Rails.env][:secret] 9 | else 10 | Pusher.app_id = ENV["PUSHER_APP_ID"] 11 | Pusher.key = ENV["PUSHER_KEY"] 12 | Pusher.secret = ENV["PUSHER_SECRET"] 13 | end 14 | 15 | unless Rails.env.test? 16 | raise "pusher app_id is required" if Pusher.app_id.blank? 17 | raise "pusher key is required" if Pusher.key.blank? 18 | raise "pusher secret is required" if Pusher.secret.blank? 19 | end 20 | 21 | Pusher.logger = Rails.logger 22 | -------------------------------------------------------------------------------- /config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure your secret_key_base is kept private 11 | # if you're sharing your code publicly. 12 | Gitpeach::Application.config.secret_key_base = 'ef0a70620d35aab2bf68968cf9577e7d05e8d7bf337f867487659a0a2d8d99e85ff5a23492ef25707d202890057f4bb5e0a5c418a02fd03fe1d46a436e6e97cc' 13 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_gitpeach_session' 4 | -------------------------------------------------------------------------------- /config/initializers/url_for_with_unescape_id.rb: -------------------------------------------------------------------------------- 1 | # unescape url encoded id for rails 4.1.2+ 2 | # 3 | # via. 4 | # * https://github.com/rails/rails/blob/v4.1.2/actionpack/CHANGELOG.md 5 | # * https://github.com/rails/rails/commit/8a067640e6fe222022dc77bb63d5da37ef75a189 6 | 7 | def url_for_with_unescape_id(options = nil) 8 | path = url_for_without_unescape_id(options) 9 | 10 | # unescape: %2F -> / 11 | path.gsub(%r{([a-zA-Z.0-9_\-]+)%2F([a-zA-Z.0-9_\-]+)}){ $1 + "/" + $2 } 12 | end 13 | 14 | module ActionView 15 | module RoutingUrlFor 16 | alias_method_chain :url_for, :unescape_id 17 | end 18 | end 19 | 20 | class ActionController::Base 21 | alias_method_chain :url_for, :unescape_id 22 | end 23 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /config/locales/en.bootstrap.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | helpers: 6 | actions: "Actions" 7 | links: 8 | back: "Back" 9 | cancel: "Cancel" 10 | confirm: "Are you sure?" 11 | destroy: "Delete" 12 | new: "New" 13 | edit: "Edit" 14 | titles: 15 | edit: "Edit %{model}" 16 | save: "Save %{model}" 17 | new: "New %{model}" 18 | delete: "Delete %{model}" 19 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /config/locales/ja.bootstrap.yml: -------------------------------------------------------------------------------- 1 | # https://gist.github.com/sue445/5261654 2 | 3 | ja: 4 | helpers: 5 | actions: "Actions" 6 | links: 7 | back: "戻る" 8 | cancel: "キャンセル" 9 | confirm: "本当にいいですか?" 10 | destroy: "削除" 11 | new: "新規" 12 | titles: 13 | edit: "編集" 14 | save: "保存" 15 | new: "新規" 16 | delete: "削除" 17 | -------------------------------------------------------------------------------- /config/locales/ja.yml: -------------------------------------------------------------------------------- 1 | ja: 2 | date: 3 | abbr_day_names: 4 | - 日 5 | - 月 6 | - 火 7 | - 水 8 | - 木 9 | - 金 10 | - 土 11 | abbr_month_names: 12 | - 13 | - 1月 14 | - 2月 15 | - 3月 16 | - 4月 17 | - 5月 18 | - 6月 19 | - 7月 20 | - 8月 21 | - 9月 22 | - 10月 23 | - 11月 24 | - 12月 25 | day_names: 26 | - 日曜日 27 | - 月曜日 28 | - 火曜日 29 | - 水曜日 30 | - 木曜日 31 | - 金曜日 32 | - 土曜日 33 | formats: 34 | default: ! '%Y/%m/%d' 35 | long: ! '%Y年%m月%d日(%a)' 36 | short: ! '%m/%d' 37 | month_names: 38 | - 39 | - 1月 40 | - 2月 41 | - 3月 42 | - 4月 43 | - 5月 44 | - 6月 45 | - 7月 46 | - 8月 47 | - 9月 48 | - 10月 49 | - 11月 50 | - 12月 51 | order: 52 | - :year 53 | - :month 54 | - :day 55 | datetime: 56 | distance_in_words: 57 | about_x_hours: 58 | one: 約1時間 59 | other: 約%{count}時間 60 | about_x_months: 61 | one: 約1ヶ月 62 | other: 約%{count}ヶ月 63 | about_x_years: 64 | one: 約1年 65 | other: 約%{count}年 66 | almost_x_years: 67 | one: 1年弱 68 | other: ! '%{count}年弱' 69 | half_a_minute: 30秒前後 70 | less_than_x_minutes: 71 | one: 1分以内 72 | other: ! '%{count}分未満' 73 | less_than_x_seconds: 74 | one: 1秒以内 75 | other: ! '%{count}秒未満' 76 | over_x_years: 77 | one: 1年以上 78 | other: ! '%{count}年以上' 79 | x_days: 80 | one: 1日 81 | other: ! '%{count}日' 82 | x_minutes: 83 | one: 1分 84 | other: ! '%{count}分' 85 | x_months: 86 | one: 1ヶ月 87 | other: ! '%{count}ヶ月' 88 | x_seconds: 89 | one: 1秒 90 | other: ! '%{count}秒' 91 | prompts: 92 | day: 日 93 | hour: 時 94 | minute: 分 95 | month: 月 96 | second: 秒 97 | year: 年 98 | errors: &errors 99 | format: ! '%{attribute}%{message}' 100 | messages: 101 | accepted: を受諾してください。 102 | blank: を入力してください。 103 | present: は入力しないでください。 104 | confirmation: と%{attribute}の入力が一致しません。 105 | empty: を入力してください。 106 | equal_to: は%{count}にしてください。 107 | even: は偶数にしてください。 108 | exclusion: は予約されています。 109 | greater_than: は%{count}より大きい値にしてください。 110 | greater_than_or_equal_to: は%{count}以上の値にしてください。 111 | inclusion: は一覧にありません。 112 | invalid: は不正な値です。 113 | less_than: は%{count}より小さい値にしてください。 114 | less_than_or_equal_to: は%{count}以下の値にしてください。 115 | not_a_number: は数値で入力してください。 116 | not_an_integer: は整数で入力してください。 117 | odd: は奇数にしてください。 118 | record_invalid: バリデーションに失敗しました。 %{errors} 119 | restrict_dependent_destroy: ! '%{record}が存在しているので削除できません。' 120 | taken: はすでに存在します。 121 | too_long: は%{count}文字以内で入力してください。 122 | too_short: は%{count}文字以上で入力してください。 123 | wrong_length: は%{count}文字で入力してください。 124 | other_than: "は%{count}以外の値にしてください。" 125 | template: 126 | body: 次の項目を確認してください。 127 | header: 128 | one: ! '%{model}にエラーが発生しました。' 129 | other: ! '%{model}に%{count}個のエラーが発生しました。' 130 | helpers: 131 | select: 132 | prompt: 選択してください。 133 | submit: 134 | create: 登録する 135 | submit: 保存する 136 | update: 更新する 137 | number: 138 | currency: 139 | format: 140 | delimiter: ! ',' 141 | format: ! '%n%u' 142 | precision: 0 143 | separator: . 144 | significant: false 145 | strip_insignificant_zeros: false 146 | unit: 円 147 | format: 148 | delimiter: ! ',' 149 | precision: 3 150 | separator: . 151 | significant: false 152 | strip_insignificant_zeros: false 153 | human: 154 | decimal_units: 155 | format: ! '%n %u' 156 | units: 157 | billion: 十億 158 | million: 百万 159 | quadrillion: 千兆 160 | thousand: 千 161 | trillion: 兆 162 | unit: '' 163 | format: 164 | delimiter: '' 165 | precision: 3 166 | significant: true 167 | strip_insignificant_zeros: true 168 | storage_units: 169 | format: ! '%n%u' 170 | units: 171 | byte: バイト 172 | gb: ギガバイト 173 | kb: キロバイト 174 | mb: メガバイト 175 | tb: テラバイト 176 | percentage: 177 | format: 178 | delimiter: '' 179 | format: "%n%" 180 | precision: 181 | format: 182 | delimiter: '' 183 | support: 184 | array: 185 | last_word_connector: と 186 | two_words_connector: と 187 | words_connector: と 188 | time: 189 | am: 午前 190 | formats: 191 | default: ! '%Y/%m/%d %H:%M:%S' 192 | long: ! '%Y年%m月%d日(%a) %H時%M分%S秒 %z' 193 | short: ! '%y/%m/%d %H:%M' 194 | pm: 午後 195 | # remove these aliases after 'activemodel' and 'activerecord' namespaces are removed from Rails repository 196 | activemodel: 197 | errors: 198 | <<: *errors 199 | activerecord: 200 | errors: 201 | <<: *errors 202 | -------------------------------------------------------------------------------- /config/pusher.yml.sample: -------------------------------------------------------------------------------- 1 | development: &development 2 | app_id: your-pusher-app-id 3 | key: your-pusher-key 4 | secret: your-pusher-secret 5 | 6 | test: 7 | <<: *development 8 | 9 | production: 10 | <<: *development 11 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Gitpeach::Application.routes.draw do 2 | post "login" => "sessions#create" , as: :login 3 | get "logout" => "sessions#destroy", as: :logout 4 | 5 | # The priority is based upon order of creation: first created -> highest priority. 6 | # See how all your routes lay out with "rake routes". 7 | 8 | # You can have the root of your site routed with "root" 9 | root 'top#index' 10 | 11 | resources :kanbans, constraints: { id: /[a-zA-Z.0-9_\-]+\/[a-zA-Z.0-9_\-]+/ }, only: [:show, :create, :destroy, :edit, :update], path: "/" do 12 | member do 13 | get :sync 14 | end 15 | 16 | resources :issues, constraints: {id: /\d+/}, only: [:show, :update, :create] do 17 | end 18 | end 19 | 20 | # Example of regular route: 21 | # get 'products/:id' => 'catalog#view' 22 | 23 | # Example of named route that can be invoked with purchase_url(id: product.id) 24 | # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase 25 | 26 | # Example resource route (maps HTTP verbs to controller actions automatically): 27 | # resources :products 28 | 29 | # Example resource route with options: 30 | # resources :products do 31 | # member do 32 | # get 'short' 33 | # post 'toggle' 34 | # end 35 | # 36 | # collection do 37 | # get 'sold' 38 | # end 39 | # end 40 | 41 | # Example resource route with sub-resources: 42 | # resources :products do 43 | # resources :comments, :sales 44 | # resource :seller 45 | # end 46 | 47 | # Example resource route with more complex sub-resources: 48 | # resources :products do 49 | # resources :comments 50 | # resources :sales do 51 | # get 'recent', on: :collection 52 | # end 53 | # end 54 | 55 | # Example resource route with concerns: 56 | # concern :toggleable do 57 | # post 'toggle' 58 | # end 59 | # resources :posts, concerns: :toggleable 60 | # resources :photos, concerns: :toggleable 61 | 62 | # Example resource route within a namespace: 63 | # namespace :admin do 64 | # # Directs /admin/products/* to Admin::ProductsController 65 | # # (app/controllers/admin/products_controller.rb) 66 | # resources :products 67 | # end 68 | end 69 | -------------------------------------------------------------------------------- /config/unicorn.rb: -------------------------------------------------------------------------------- 1 | # Sample configuration file for Unicorn (not Rack) 2 | # 3 | # See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete 4 | # documentation. 5 | 6 | APP_DIR = File.expand_path("../../", __FILE__) 7 | UNICORN_USER = "www-data" 8 | UNICORN_GROUP = "www-data" 9 | 10 | # Use at least one worker per core if you're on a dedicated server, 11 | # more will usually help for _short_ waits on databases/caches. 12 | worker_processes 4 13 | 14 | # Help ensure your application will always spawn in the symlinked 15 | # "current" directory that Capistrano sets up. 16 | # working_directory "<%= unicorn_working_directory %>" # available in 0.94.0+ 17 | 18 | # listen on both a Unix domain socket and a TCP port, 19 | # we use a shorter backlog for quicker failover when busy 20 | listen "#{APP_DIR}/tmp/unicorn.sock", :backlog => 128 21 | listen 8080, :tcp_nopush => true 22 | 23 | # nuke workers after 30 seconds instead of 60 seconds (the default) 24 | timeout 30 25 | 26 | # feel free to point this anywhere accessible on the filesystem 27 | #pid '/tmp/pids/unicorn.pid' 28 | 29 | # some applications/frameworks log to stderr or stdout, so prevent 30 | # them from going to /dev/null when daemonized here: 31 | stderr_path "#{APP_DIR}/log/unicorn.stderr.log" 32 | stdout_path "#{APP_DIR}/log/unicorn.stdout.log" 33 | 34 | # combine REE with "preload_app true" for memory savings 35 | # http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow 36 | preload_app true 37 | GC.respond_to?(:copy_on_write_friendly=) and GC.copy_on_write_friendly = true 38 | 39 | before_fork do |server, worker| 40 | # the following is highly recomended for Rails + "preload_app true" 41 | # as there's no need for the master process to hold a connection 42 | defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect! 43 | defined?(Resque) and Resque.redis and Resque.redis.client.disconnect 44 | 45 | # The following is only recommended for memory/DB-constrained 46 | # installations. It is not needed if your system can house 47 | # twice as many worker_processes as you have configured. 48 | 49 | # This allows a new master process to incrementally 50 | # phase out the old master process with SIGTTOU to avoid a 51 | # thundering herd (especially in the "preload_app false" case) 52 | # when doing a transparent upgrade. The last worker spawned 53 | # will then kill off the old master process with a SIGQUIT. 54 | old_pid = "#{server.config[:pid]}.oldbin" 55 | if File.exists?(old_pid) && server.pid != old_pid 56 | begin 57 | Process.kill("QUIT", File.read(old_pid).to_i) 58 | rescue Errno::ENOENT, Errno::ESRCH 59 | # someone else did our job for us 60 | end 61 | end 62 | 63 | # # *optionally* throttle the master from forking too quickly by sleeping 64 | # sleep 1 65 | end 66 | 67 | after_fork do |server, worker| 68 | # per-process listener ports for debugging/admin/migrations 69 | # addr = "127.0.0.1:#{9293 + worker.nr}" 70 | # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true) 71 | 72 | # the following is *required* for Rails + "preload_app true", 73 | defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection 74 | # TODO uncomment out 75 | #defined?(Resque) and Resque.redis and Resque.redis.client.connect 76 | 77 | # if preload_app is true, then you may also want to check and # restart any other shared sockets/descriptors such as Memcached, 78 | # and Redis. TokyoCabinet file handles are safe to reuse 79 | # between any number of forked children (assuming your kernel correctly implements pread()/pwrite() system calls) 80 | worker.user(UNICORN_USER, UNICORN_GROUP) if Process.euid == 0 81 | end 82 | -------------------------------------------------------------------------------- /db/migrate/20140110175259_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration 2 | def change 3 | create_table :users do |t| 4 | t.integer :gitlab_user_id 5 | t.string :username 6 | t.string :email 7 | t.string :private_token 8 | 9 | t.timestamps 10 | end 11 | 12 | add_index :users, :gitlab_user_id, unique: true 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20140112144617_create_friendly_id_slugs.rb: -------------------------------------------------------------------------------- 1 | class CreateFriendlyIdSlugs < ActiveRecord::Migration 2 | def change 3 | create_table :friendly_id_slugs do |t| 4 | t.string :slug, :null => false 5 | t.integer :sluggable_id, :null => false 6 | t.string :sluggable_type, :limit => 50 7 | t.string :scope 8 | t.datetime :created_at 9 | end 10 | add_index :friendly_id_slugs, :sluggable_id 11 | add_index :friendly_id_slugs, [:slug, :sluggable_type] 12 | add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], :unique => true 13 | add_index :friendly_id_slugs, :sluggable_type 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20140112145704_create_kanbans.rb: -------------------------------------------------------------------------------- 1 | class CreateKanbans < ActiveRecord::Migration 2 | def change 3 | create_table :kanbans do |t| 4 | t.integer :gitlab_project_id 5 | t.string :name 6 | t.string :slug 7 | 8 | t.timestamps 9 | end 10 | 11 | add_index :kanbans, :slug, unique: true 12 | add_index :kanbans, :name, unique: true 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20140112164427_create_labels.rb: -------------------------------------------------------------------------------- 1 | class CreateLabels < ActiveRecord::Migration 2 | def change 3 | create_table :labels do |t| 4 | t.integer :kanban_id 5 | t.string :name 6 | t.string :gitlab_label 7 | t.integer :disp_order 8 | t.boolean :is_backlog_issue 9 | t.boolean :is_close_issue 10 | 11 | t.timestamps 12 | end 13 | 14 | add_index :labels, [:kanban_id, :disp_order] 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sue445/gitpeach/08096149c8d768c98159c9061cb324a4b2663be1/lib/assets/.keep -------------------------------------------------------------------------------- /lib/support/init.d/unicorn_gitpeach: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # refs: http://gist.github.com/237953 3 | set -u 4 | set -e 5 | 6 | APP_ROOT=/var/www/gitpeach 7 | CONF=$APP_ROOT/config/unicorn.conf 8 | USAGE="Usage: $0 <(production=default|staging)> " 9 | ENVIRONMENT=production 10 | 11 | cd $APP_ROOT || exit 1 12 | 13 | if [ $# -eq 0 ]; then 14 | echo >&2 $USAGE 15 | exit 0 16 | else 17 | echo "$1 ....... " 18 | fi 19 | 20 | ENV=$ENVIRONMENT 21 | if [ $# -gt 1 ] ; then 22 | case $2 in 23 | production) 24 | ENV=production 25 | ;; 26 | staging) 27 | ENV=staging 28 | ;; 29 | stress) 30 | ENV=stress 31 | ;; 32 | development) 33 | ENV=development 34 | ;; 35 | *) 36 | ENV=$ENVIRONMENT 37 | ;; 38 | esac 39 | fi 40 | 41 | GEMFILE=$APP_ROOT/Gemfile 42 | SCRIPT=$APP_ROOT/bin/unicorn 43 | 44 | CMD_START="$SCRIPT start -d -e $ENV" 45 | CMD_STOP="$SCRIPT stop" 46 | CMD_KILL="$SCRIPT kill" 47 | CMD_RESTART="$SCRIPT graceful" 48 | CMD_ROTATE="$SCRIPT rotatelog" 49 | 50 | SUCCESS="$1 success." 51 | 52 | # run command 53 | case $1 in 54 | start) 55 | BUNDLE_GEMFILE=$GEMFILE $CMD_START 56 | echo $SUCCESS 57 | ;; 58 | stop) 59 | $CMD_STOP 60 | echo $SUCCESS 61 | ;; 62 | force-stop) 63 | $CMD_KILL 64 | echo $SUCCESS 65 | ;; 66 | restart|reload) 67 | $CMD_RESTART 68 | echo $SUCCESS 69 | ;; 70 | rotate) 71 | $CMD_ROTATE 72 | echo $SUCCESS 73 | ;; 74 | *) 75 | echo >&2 $USAGE 76 | exit 1 77 | ;; 78 | esac 79 | 80 | -------------------------------------------------------------------------------- /lib/support/logrotate.d/gitpeach: -------------------------------------------------------------------------------- 1 | /var/www/gitpeach/log/*.log { 2 | daily 3 | dateext 4 | missingok 5 | rotate 14 6 | compress 7 | notifempty 8 | copytruncate 9 | olddir /var/www/gitpeach/log/old 10 | } 11 | -------------------------------------------------------------------------------- /lib/support/nginx/gitpeach: -------------------------------------------------------------------------------- 1 | # statements for each of your virtual hosts 2 | upstream gitpeach { 3 | server 127.0.0.1:8080; 4 | } 5 | 6 | server { 7 | listen 80 default; 8 | 9 | #listen 80 ; 10 | #listen 443 default_server ssl; 11 | #listen 443 default ssl; 12 | # TODO edit here 13 | server_name peach.example.com; 14 | 15 | #ssl on; 16 | #ssl_certificate /etc/nginx/conf.d/cert.pem; 17 | #ssl_certificate_key /etc/nginx/conf.d/cert.key; 18 | 19 | access_log /var/log/nginx/gitpeach_access.log combined; 20 | error_log /var/log/nginx/gitpeach_error.log warn; 21 | 22 | location / { 23 | root /var/www/gitpeach/public; 24 | 25 | if (-f $request_filename) { 26 | access_log off; 27 | rewrite_log off; 28 | expires 1h; 29 | break; 30 | } 31 | 32 | proxy_set_header X-Forwarded-Proto $scheme; 33 | # proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; 34 | proxy_set_header X-Real-IP $remote_addr; 35 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 36 | proxy_set_header Host $host; 37 | proxy_redirect off; 38 | #proxy_read_timeout 8; 39 | #proxy_connect_timeout 2; 40 | send_timeout 300; 41 | proxy_read_timeout 300; 42 | proxy_connect_timeout 300; 43 | proxy_buffer_size 16k; 44 | proxy_buffers 32 16k; 45 | proxy_busy_buffers_size 64k; 46 | 47 | if (!-f $request_filename) { 48 | proxy_pass http://gitpeach; 49 | break; 50 | } 51 | } 52 | 53 | location ~ /\.git { 54 | deny all; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sue445/gitpeach/08096149c8d768c98159c9061cb324a4b2663be1/lib/tasks/.keep -------------------------------------------------------------------------------- /lib/tasks/auto_annotate_models.rake: -------------------------------------------------------------------------------- 1 | # via. https://github.com/ctran/annotate_models/wiki 2 | 3 | if(Rails.env.development?) 4 | task :set_annotation_options do 5 | Annotate.set_defaults( 6 | exclude_tests: true, 7 | exclude_fixtures: true, 8 | exclude_factories: true, 9 | show_indexes: true, 10 | ) 11 | end 12 | 13 | # Comes with the current master when running `rails g annotate:install` 14 | # But somehow won't annotate my models correctly (only one) 15 | # Thus commented out 16 | # Annotate.load_tasks 17 | 18 | # Annotate models 19 | task :annotate do 20 | puts 'Annotating models...' 21 | system 'bundle exec annotate' 22 | end 23 | 24 | # Run annotate task after db:migrate 25 | # and db:rollback tasks 26 | Rake::Task['db:migrate'].enhance do 27 | Rake::Task['annotate'].invoke 28 | end 29 | 30 | Rake::Task['db:rollback'].enhance do 31 | Rake::Task['annotate'].invoke 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sue445/gitpeach/08096149c8d768c98159c9061cb324a4b2663be1/log/.keep -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 48 | 49 | 50 | 51 | 52 |
53 |

The page you were looking for doesn't exist.

54 |

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

55 |
56 |

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

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

The change you wanted was rejected.

54 |

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

55 |
56 |

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

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

We're sorry, but something went wrong.

54 |
55 |

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

56 | 57 | 58 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sue445/gitpeach/08096149c8d768c98159c9061cb324a4b2663be1/public/favicon.ico -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sue445/gitpeach/08096149c8d768c98159c9061cb324a4b2663be1/public/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sue445/gitpeach/08096149c8d768c98159c9061cb324a4b2663be1/public/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sue445/gitpeach/08096149c8d768c98159c9061cb324a4b2663be1/public/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /public/javascripts/jquery_ujs.js: -------------------------------------------------------------------------------- 1 | (function($, undefined) { 2 | 3 | /** 4 | * Unobtrusive scripting adapter for jQuery 5 | * https://github.com/rails/jquery-ujs 6 | * 7 | * Requires jQuery 1.7.0 or later. 8 | * 9 | * Released under the MIT license 10 | * 11 | */ 12 | 13 | // Cut down on the number of issues from people inadvertently including jquery_ujs twice 14 | // by detecting and raising an error when it happens. 15 | if ( $.rails !== undefined ) { 16 | $.error('jquery-ujs has already been loaded!'); 17 | } 18 | 19 | // Shorthand to make it a little easier to call public rails functions from within rails.js 20 | var rails; 21 | var $document = $(document); 22 | 23 | $.rails = rails = { 24 | // Link elements bound by jquery-ujs 25 | linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote], a[data-disable-with]', 26 | 27 | // Button elements boud jquery-ujs 28 | buttonClickSelector: 'button[data-remote]', 29 | 30 | // Select elements bound by jquery-ujs 31 | inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]', 32 | 33 | // Form elements bound by jquery-ujs 34 | formSubmitSelector: 'form', 35 | 36 | // Form input elements bound by jquery-ujs 37 | formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])', 38 | 39 | // Form input elements disabled during form submission 40 | disableSelector: 'input[data-disable-with], button[data-disable-with], textarea[data-disable-with]', 41 | 42 | // Form input elements re-enabled after form submission 43 | enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled', 44 | 45 | // Form required input elements 46 | requiredInputSelector: 'input[name][required]:not([disabled]),textarea[name][required]:not([disabled])', 47 | 48 | // Form file input elements 49 | fileInputSelector: 'input[type=file]', 50 | 51 | // Link onClick disable selector with possible reenable after remote submission 52 | linkDisableSelector: 'a[data-disable-with]', 53 | 54 | // Make sure that every Ajax request sends the CSRF token 55 | CSRFProtection: function(xhr) { 56 | var token = $('meta[name="csrf-token"]').attr('content'); 57 | if (token) xhr.setRequestHeader('X-CSRF-Token', token); 58 | }, 59 | 60 | // Triggers an event on an element and returns false if the event result is false 61 | fire: function(obj, name, data) { 62 | var event = $.Event(name); 63 | obj.trigger(event, data); 64 | return event.result !== false; 65 | }, 66 | 67 | // Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm 68 | confirm: function(message) { 69 | return confirm(message); 70 | }, 71 | 72 | // Default ajax function, may be overridden with custom function in $.rails.ajax 73 | ajax: function(options) { 74 | return $.ajax(options); 75 | }, 76 | 77 | // Default way to get an element's href. May be overridden at $.rails.href. 78 | href: function(element) { 79 | return element.attr('href'); 80 | }, 81 | 82 | // Submits "remote" forms and links with ajax 83 | handleRemote: function(element) { 84 | var method, url, data, elCrossDomain, crossDomain, withCredentials, dataType, options; 85 | 86 | if (rails.fire(element, 'ajax:before')) { 87 | elCrossDomain = element.data('cross-domain'); 88 | crossDomain = elCrossDomain === undefined ? null : elCrossDomain; 89 | withCredentials = element.data('with-credentials') || null; 90 | dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType); 91 | 92 | if (element.is('form')) { 93 | method = element.attr('method'); 94 | url = element.attr('action'); 95 | data = element.serializeArray(); 96 | // memoized value from clicked submit button 97 | var button = element.data('ujs:submit-button'); 98 | if (button) { 99 | data.push(button); 100 | element.data('ujs:submit-button', null); 101 | } 102 | } else if (element.is(rails.inputChangeSelector)) { 103 | method = element.data('method'); 104 | url = element.data('url'); 105 | data = element.serialize(); 106 | if (element.data('params')) data = data + "&" + element.data('params'); 107 | } else if (element.is(rails.buttonClickSelector)) { 108 | method = element.data('method') || 'get'; 109 | url = element.data('url'); 110 | data = element.serialize(); 111 | if (element.data('params')) data = data + "&" + element.data('params'); 112 | } else { 113 | method = element.data('method'); 114 | url = rails.href(element); 115 | data = element.data('params') || null; 116 | } 117 | 118 | options = { 119 | type: method || 'GET', data: data, dataType: dataType, 120 | // stopping the "ajax:beforeSend" event will cancel the ajax request 121 | beforeSend: function(xhr, settings) { 122 | if (settings.dataType === undefined) { 123 | xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script); 124 | } 125 | return rails.fire(element, 'ajax:beforeSend', [xhr, settings]); 126 | }, 127 | success: function(data, status, xhr) { 128 | element.trigger('ajax:success', [data, status, xhr]); 129 | }, 130 | complete: function(xhr, status) { 131 | element.trigger('ajax:complete', [xhr, status]); 132 | }, 133 | error: function(xhr, status, error) { 134 | element.trigger('ajax:error', [xhr, status, error]); 135 | }, 136 | crossDomain: crossDomain 137 | }; 138 | 139 | // There is no withCredentials for IE6-8 when 140 | // "Enable native XMLHTTP support" is disabled 141 | if (withCredentials) { 142 | options.xhrFields = { 143 | withCredentials: withCredentials 144 | }; 145 | } 146 | 147 | // Only pass url to `ajax` options if not blank 148 | if (url) { options.url = url; } 149 | 150 | var jqxhr = rails.ajax(options); 151 | element.trigger('ajax:send', jqxhr); 152 | return jqxhr; 153 | } else { 154 | return false; 155 | } 156 | }, 157 | 158 | // Handles "data-method" on links such as: 159 | //
Delete 160 | handleMethod: function(link) { 161 | var href = rails.href(link), 162 | method = link.data('method'), 163 | target = link.attr('target'), 164 | csrf_token = $('meta[name=csrf-token]').attr('content'), 165 | csrf_param = $('meta[name=csrf-param]').attr('content'), 166 | form = $('
'), 167 | metadata_input = ''; 168 | 169 | if (csrf_param !== undefined && csrf_token !== undefined) { 170 | metadata_input += ''; 171 | } 172 | 173 | if (target) { form.attr('target', target); } 174 | 175 | form.hide().append(metadata_input).appendTo('body'); 176 | form.submit(); 177 | }, 178 | 179 | /* Disables form elements: 180 | - Caches element value in 'ujs:enable-with' data store 181 | - Replaces element text with value of 'data-disable-with' attribute 182 | - Sets disabled property to true 183 | */ 184 | disableFormElements: function(form) { 185 | form.find(rails.disableSelector).each(function() { 186 | var element = $(this), method = element.is('button') ? 'html' : 'val'; 187 | element.data('ujs:enable-with', element[method]()); 188 | element[method](element.data('disable-with')); 189 | element.prop('disabled', true); 190 | }); 191 | }, 192 | 193 | /* Re-enables disabled form elements: 194 | - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`) 195 | - Sets disabled property to false 196 | */ 197 | enableFormElements: function(form) { 198 | form.find(rails.enableSelector).each(function() { 199 | var element = $(this), method = element.is('button') ? 'html' : 'val'; 200 | if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with')); 201 | element.prop('disabled', false); 202 | }); 203 | }, 204 | 205 | /* For 'data-confirm' attribute: 206 | - Fires `confirm` event 207 | - Shows the confirmation dialog 208 | - Fires the `confirm:complete` event 209 | 210 | Returns `true` if no function stops the chain and user chose yes; `false` otherwise. 211 | Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog. 212 | Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function 213 | return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog. 214 | */ 215 | allowAction: function(element) { 216 | var message = element.data('confirm'), 217 | answer = false, callback; 218 | if (!message) { return true; } 219 | 220 | if (rails.fire(element, 'confirm')) { 221 | answer = rails.confirm(message); 222 | callback = rails.fire(element, 'confirm:complete', [answer]); 223 | } 224 | return answer && callback; 225 | }, 226 | 227 | // Helper function which checks for blank inputs in a form that match the specified CSS selector 228 | blankInputs: function(form, specifiedSelector, nonBlank) { 229 | var inputs = $(), input, valueToCheck, 230 | selector = specifiedSelector || 'input,textarea', 231 | allInputs = form.find(selector); 232 | 233 | allInputs.each(function() { 234 | input = $(this); 235 | valueToCheck = input.is('input[type=checkbox],input[type=radio]') ? input.is(':checked') : input.val(); 236 | // If nonBlank and valueToCheck are both truthy, or nonBlank and valueToCheck are both falsey 237 | if (!valueToCheck === !nonBlank) { 238 | 239 | // Don't count unchecked required radio if other radio with same name is checked 240 | if (input.is('input[type=radio]') && allInputs.filter('input[type=radio]:checked[name="' + input.attr('name') + '"]').length) { 241 | return true; // Skip to next input 242 | } 243 | 244 | inputs = inputs.add(input); 245 | } 246 | }); 247 | return inputs.length ? inputs : false; 248 | }, 249 | 250 | // Helper function which checks for non-blank inputs in a form that match the specified CSS selector 251 | nonBlankInputs: function(form, specifiedSelector) { 252 | return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank 253 | }, 254 | 255 | // Helper function, needed to provide consistent behavior in IE 256 | stopEverything: function(e) { 257 | $(e.target).trigger('ujs:everythingStopped'); 258 | e.stopImmediatePropagation(); 259 | return false; 260 | }, 261 | 262 | // replace element's html with the 'data-disable-with' after storing original html 263 | // and prevent clicking on it 264 | disableElement: function(element) { 265 | element.data('ujs:enable-with', element.html()); // store enabled state 266 | element.html(element.data('disable-with')); // set to disabled state 267 | element.bind('click.railsDisable', function(e) { // prevent further clicking 268 | return rails.stopEverything(e); 269 | }); 270 | }, 271 | 272 | // restore element to its original state which was disabled by 'disableElement' above 273 | enableElement: function(element) { 274 | if (element.data('ujs:enable-with') !== undefined) { 275 | element.html(element.data('ujs:enable-with')); // set to old enabled state 276 | element.removeData('ujs:enable-with'); // clean up cache 277 | } 278 | element.unbind('click.railsDisable'); // enable element 279 | } 280 | 281 | }; 282 | 283 | if (rails.fire($document, 'rails:attachBindings')) { 284 | 285 | $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }}); 286 | 287 | $document.delegate(rails.linkDisableSelector, 'ajax:complete', function() { 288 | rails.enableElement($(this)); 289 | }); 290 | 291 | $document.delegate(rails.linkClickSelector, 'click.rails', function(e) { 292 | var link = $(this), method = link.data('method'), data = link.data('params'); 293 | if (!rails.allowAction(link)) return rails.stopEverything(e); 294 | 295 | if (link.is(rails.linkDisableSelector)) rails.disableElement(link); 296 | 297 | if (link.data('remote') !== undefined) { 298 | if ( (e.metaKey || e.ctrlKey) && (!method || method === 'GET') && !data ) { return true; } 299 | 300 | var handleRemote = rails.handleRemote(link); 301 | // response from rails.handleRemote() will either be false or a deferred object promise. 302 | if (handleRemote === false) { 303 | rails.enableElement(link); 304 | } else { 305 | handleRemote.error( function() { rails.enableElement(link); } ); 306 | } 307 | return false; 308 | 309 | } else if (link.data('method')) { 310 | rails.handleMethod(link); 311 | return false; 312 | } 313 | }); 314 | 315 | $document.delegate(rails.buttonClickSelector, 'click.rails', function(e) { 316 | var button = $(this); 317 | if (!rails.allowAction(button)) return rails.stopEverything(e); 318 | 319 | rails.handleRemote(button); 320 | return false; 321 | }); 322 | 323 | $document.delegate(rails.inputChangeSelector, 'change.rails', function(e) { 324 | var link = $(this); 325 | if (!rails.allowAction(link)) return rails.stopEverything(e); 326 | 327 | rails.handleRemote(link); 328 | return false; 329 | }); 330 | 331 | $document.delegate(rails.formSubmitSelector, 'submit.rails', function(e) { 332 | var form = $(this), 333 | remote = form.data('remote') !== undefined, 334 | blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector), 335 | nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector); 336 | 337 | if (!rails.allowAction(form)) return rails.stopEverything(e); 338 | 339 | // skip other logic when required values are missing or file upload is present 340 | if (blankRequiredInputs && form.attr("novalidate") == undefined && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) { 341 | return rails.stopEverything(e); 342 | } 343 | 344 | if (remote) { 345 | if (nonBlankFileInputs) { 346 | // slight timeout so that the submit button gets properly serialized 347 | // (make it easy for event handler to serialize form without disabled values) 348 | setTimeout(function(){ rails.disableFormElements(form); }, 13); 349 | var aborted = rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]); 350 | 351 | // re-enable form elements if event bindings return false (canceling normal form submission) 352 | if (!aborted) { setTimeout(function(){ rails.enableFormElements(form); }, 13); } 353 | 354 | return aborted; 355 | } 356 | 357 | rails.handleRemote(form); 358 | return false; 359 | 360 | } else { 361 | // slight timeout so that the submit button gets properly serialized 362 | setTimeout(function(){ rails.disableFormElements(form); }, 13); 363 | } 364 | }); 365 | 366 | $document.delegate(rails.formInputClickSelector, 'click.rails', function(event) { 367 | var button = $(this); 368 | 369 | if (!rails.allowAction(button)) return rails.stopEverything(event); 370 | 371 | // register the pressed submit button 372 | var name = button.attr('name'), 373 | data = name ? {name:name, value:button.val()} : null; 374 | 375 | button.closest('form').data('ujs:submit-button', data); 376 | }); 377 | 378 | $document.delegate(rails.formSubmitSelector, 'ajax:beforeSend.rails', function(event) { 379 | if (this == event.target) rails.disableFormElements($(this)); 380 | }); 381 | 382 | $document.delegate(rails.formSubmitSelector, 'ajax:complete.rails', function(event) { 383 | if (this == event.target) rails.enableFormElements($(this)); 384 | }); 385 | 386 | $(function(){ 387 | // making sure that all forms have actual up-to-date token(cached forms contain old one) 388 | var csrf_token = $('meta[name=csrf-token]').attr('content'); 389 | var csrf_param = $('meta[name=csrf-param]').attr('content'); 390 | $('form input[name="' + csrf_param + '"]').val(csrf_token); 391 | }); 392 | } 393 | 394 | })( jQuery ); 395 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /script/build_for_jenkins.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | readonly JENKINS_RAILS_ENV="test" 4 | export LANG=ja_JP.UTF-8 5 | 6 | # Jenkins build script 7 | 8 | run() 9 | { 10 | command=$1 11 | echo "$command" 12 | 13 | eval $command 14 | 15 | # if error code returned, exit this script with error code 16 | RET=$? 17 | if [ $RET -ne 0 ]; then 18 | exit $RET 19 | fi 20 | } 21 | 22 | run "which ruby" 23 | run "ruby --version" 24 | run "cp config/database.yml.jenkins config/database.yml" 25 | run "gem install bundler --no-ri --no-rdoc" 26 | 27 | 28 | ######################### 29 | echo "bundle install --path vendor/bundle" 30 | 31 | bundle install --path vendor/bundle 32 | 33 | RET=$? 34 | if [ $RET -ne 0 ]; then 35 | # if failed 'bundle install', run 'bundle update' 36 | run "bundle update" 37 | fi 38 | 39 | run "RAILS_ENV=${JENKINS_RAILS_ENV} bundle exec rake db:create" 40 | run "RAILS_ENV=${JENKINS_RAILS_ENV} bundle exec rake db:migrate" 41 | 42 | run "rm -rf reports" 43 | run "mkdir -m 777 reports/" 44 | 45 | run "RAILS_ENV=${JENKINS_RAILS_ENV} bundle exec rspec --profile > ./reports/rspec-console.log" 46 | run "ruby ./script/plot-rspec-slowest-examples.rb ./reports/rspec-console.log > ./reports/rspec-plot.csv" 47 | 48 | # * if you use mysql_partitioning, use don't use `rake spec`. because primary key is dropped 49 | # * `rake spec` can not use --profile 50 | #run "RAILS_ENV=${JENKINS_RAILS_ENV} bundle exec rake ci:setup:rspec spec" 51 | 52 | 53 | exit 0 54 | -------------------------------------------------------------------------------- /script/plot-rspec-slowest-examples.rb: -------------------------------------------------------------------------------- 1 | # extract example seconds from rspec profile log 2 | # 3 | # required: 4 | # rspec >= 2.10.0 5 | # Jenkins Plot Plugin 6 | # 7 | # usage: 8 | # rspec --profile 5 > rspec-console.log # rspec >= 2.13.0 9 | # rspec --profile > rspec-console.log # 2.10.0 <= rspec < 2.13.0 10 | # ruby plot-rspec-slowest-examples.rb rspec-console.log > plot.csv 11 | # 12 | # and more: 13 | # https://gist.github.com/sue445/5140150 14 | 15 | if ARGV.empty? 16 | puts "usage: ruby plot-rspec-slowest-examples.rb " 17 | exit 18 | end 19 | 20 | LOG_FILE = ARGV[0] 21 | 22 | total = {} 23 | example_seconds = [] 24 | open(LOG_FILE) do |f| 25 | f.each_line do |line| 26 | case line 27 | when %r{Top (.+) slowest examples \(([0-9.]+) seconds, ([0-9.]+)% of total time\)} 28 | total[:ratio] = $3 29 | when %r{Finished in ([0-9.]+) seconds} 30 | total[:second] = $1 31 | when %r{([0-9.]+) seconds (.+)} 32 | example_seconds << $1 33 | end 34 | end 35 | end 36 | 37 | # header 38 | print "total," 39 | #print "ratio %," 40 | example_seconds.count.times do |n| 41 | print "top #{n+1}," 42 | end 43 | print "\n" 44 | 45 | # data row 46 | print "#{total[:second]}," 47 | #print "#{total[:ratio]}," 48 | example_seconds.each do |sec| 49 | print "#{sec}," 50 | end 51 | print "\n" 52 | 53 | -------------------------------------------------------------------------------- /shots/gitpeach.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sue445/gitpeach/08096149c8d768c98159c9061cb324a4b2663be1/shots/gitpeach.gif -------------------------------------------------------------------------------- /shots/issue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sue445/gitpeach/08096149c8d768c98159c9061cb324a4b2663be1/shots/issue.png -------------------------------------------------------------------------------- /shots/pusher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sue445/gitpeach/08096149c8d768c98159c9061cb324a4b2663be1/shots/pusher.png -------------------------------------------------------------------------------- /spec/controllers/issues_controller_spec.rb: -------------------------------------------------------------------------------- 1 | describe IssuesController, type: :controller do 2 | let(:kanban) { FactoryGirl.create(:kanban) } 3 | 4 | describe "POST create" do 5 | subject{ post :create, params } 6 | 7 | let(:params){ 8 | { 9 | kanban_id: kanban.name, 10 | title: "some title", 11 | } 12 | } 13 | 14 | before do 15 | allow(controller).to receive(:create_gitlab_issue){} 16 | end 17 | 18 | it "should be success" do 19 | subject 20 | expect(response).to be_success 21 | end 22 | 23 | it "should be call create_gitlab_issue with valid params" do 24 | expect(controller).to receive(:create_gitlab_issue).with(params[:title]) 25 | subject 26 | end 27 | end 28 | 29 | describe "GET show" do 30 | subject{ get :show, params } 31 | 32 | let(:params){ 33 | { 34 | id: issue_id, 35 | kanban_id: kanban.name, 36 | } 37 | } 38 | 39 | let(:issue_id){ 1 } 40 | 41 | it "should be success" do 42 | subject 43 | expect(response).to be_success 44 | end 45 | end 46 | 47 | describe "PUT update" do 48 | subject{ put :update, params } 49 | 50 | let(:params){ 51 | { 52 | id: issue_id, 53 | kanban_id: kanban.name, 54 | to_label_id: to_label.id, 55 | } 56 | } 57 | 58 | let(:issue_id){ 1 } 59 | 60 | let(:from_label){ kanban.labels.backlog.first! } 61 | let(:to_label) { kanban.labels.done.first! } 62 | 63 | before do 64 | expect(controller).to receive(:gitlab_current_issue_label_id){ from_label.id } 65 | expect(controller).to receive(:gitlab_issue_labels){ %w(bug high) } 66 | expect(controller).to receive(:update_gitlab_issue) 67 | expect(controller).to receive(:updated_issue) { {} } 68 | end 69 | 70 | it "should be success" do 71 | subject 72 | expect(response).to be_success 73 | end 74 | 75 | it "should update labels" do 76 | subject 77 | expect(assigns(:labels)).to eq %w(bug high) 78 | end 79 | 80 | it "should update state" do 81 | subject 82 | expect(assigns(:state)).to eq "close" 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /spec/controllers/kanbans_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # This spec was generated by rspec-rails when you ran the scaffold generator. 2 | # It demonstrates how one might use RSpec to specify the controller code that 3 | # was generated by Rails when you ran the scaffold generator. 4 | # 5 | # It assumes that the implementation code is generated by the rails scaffold 6 | # generator. If you are using any extension libraries to generate different 7 | # controller code, this generated spec may or may not pass. 8 | # 9 | # It only uses APIs available in rails and/or rspec-rails. There are a number 10 | # of tools you can use to make these specs even more expressive, but we're 11 | # sticking to rails and rspec-rails APIs to keep things simple and stable. 12 | # 13 | # Compared to earlier versions of this generator, there is very limited use of 14 | # stubs and message expectations in this spec. Stubs are only used when there 15 | # is no simpler way to get a handle on the object needed for the example. 16 | # Message expectations are only used when there is no simpler way to specify 17 | # that an instance is receiving a specific message. 18 | 19 | describe KanbansController, type: :controller do 20 | 21 | # This should return the minimal set of attributes required to create a valid 22 | # Kanban. As you add validations to Kanban, be sure to 23 | # adjust the attributes here as well. 24 | let(:valid_attributes) { { "gitlab_project_id" => "1", "name" => "namespace/path" } } 25 | 26 | # This should return the minimal set of values that should be in the session 27 | # in order to pass any filters (e.g. authentication) defined in 28 | # KanbansController. Be sure to keep this updated too. 29 | let(:valid_session) { {} } 30 | 31 | describe "GET show" do 32 | before do 33 | allow(controller).to receive(:project_issues).and_return([]) 34 | end 35 | 36 | it "assigns the requested kanban as @kanban" do 37 | kanban = Kanban.create! valid_attributes 38 | get :show, {:id => kanban.to_param}, valid_session 39 | expect(assigns(:kanban)).to eq(kanban) 40 | end 41 | end 42 | 43 | describe "GET edit" do 44 | it "assigns the requested kanban as @kanban" do 45 | kanban = Kanban.create! valid_attributes 46 | get :edit, {:id => kanban.to_param}, valid_session 47 | expect(assigns(:kanban)).to eq(kanban) 48 | end 49 | end 50 | 51 | describe "POST create" do 52 | it "creates a new Kanban" do 53 | expect { 54 | post :create, {:kanban => valid_attributes}, valid_session 55 | }.to change(Kanban, :count).by(1) 56 | end 57 | 58 | it "assigns a newly created kanban as @kanban" do 59 | post :create, {:kanban => valid_attributes}, valid_session 60 | expect(assigns(:kanban)).to be_a(Kanban) 61 | expect(assigns(:kanban)).to be_persisted 62 | end 63 | 64 | it "redirects to the created kanban" do 65 | post :create, {:kanban => valid_attributes}, valid_session 66 | expect(response).to redirect_to(Kanban.last) 67 | end 68 | end 69 | 70 | describe "PUT update" do 71 | subject{ put :update, params } 72 | 73 | let(:params) do 74 | { 75 | id: kanban.name, 76 | labels: labels, 77 | } 78 | end 79 | 80 | let(:labels) do 81 | [ 82 | {name: "Backlog" , gitlab_label: nil , is_backlog_issue: true , is_close_issue: false, id: backlog.id}, 83 | {name: "Ready" , gitlab_label: "ready" , is_backlog_issue: false, is_close_issue: false, id: ready.id}, 84 | {name: "In Progress", gitlab_label: "in progress", is_backlog_issue: false, is_close_issue: false, id: in_progress.id}, 85 | {name: "Done" , gitlab_label: nil , is_backlog_issue: false, is_close_issue: true , id: done.id}, 86 | ] 87 | end 88 | 89 | let!(:kanban) { FactoryGirl.create(:kanban) } 90 | let(:backlog) { kanban.labels.backlog.first } 91 | let(:ready) { kanban.labels.other.where(name: "Ready").first } 92 | let(:in_progress){ kanban.labels.other.where(name: "In Progress").first } 93 | let(:done) { kanban.labels.done.first } 94 | 95 | it "should be redirect" do 96 | subject 97 | expect(response).to be_redirect 98 | end 99 | 100 | context "When update label" do 101 | before do 102 | labels[0][:name] = "TODO" 103 | labels[1][:gitlab_label] = "ready5" 104 | end 105 | 106 | it{ expect{ subject }.to change{ Label.find(backlog.id).name }.from("Backlog").to("TODO") } 107 | it{ expect{ subject }.to change{ Label.find(ready.id).gitlab_label }.from("ready").to("ready5") } 108 | end 109 | 110 | context "When add new label" do 111 | before do 112 | labels.insert(1, new_label) 113 | end 114 | 115 | let(:new_label) do 116 | {name: "Pending", gitlab_label: "pending", is_backlog_issue: false, is_close_issue: false} 117 | end 118 | 119 | it{ expect{ subject }.to change(Label, :count).by(1) } 120 | it{ expect{ subject }.to change{ Label.find(done.id).disp_order }.from(3).to(4) } 121 | end 122 | 123 | context "When delete label" do 124 | before do 125 | labels.delete_at(2) 126 | end 127 | 128 | it{ expect{ subject }.to change(Label, :count).by(-1) } 129 | it{ expect{ subject }.to change{ Label.where(id: in_progress.id).exists? }.from(true).to(false) } 130 | end 131 | 132 | context "When swap labels" do 133 | before do 134 | labels[0], labels[1] = labels[1], labels[0] 135 | end 136 | 137 | it{ expect{ subject }.to change{ Label.find(backlog.id).disp_order }.from(0).to(1) } 138 | it{ expect{ subject }.to change{ Label.find(ready.id).disp_order }.from(1).to(0) } 139 | end 140 | end 141 | 142 | describe "DELETE destroy" do 143 | it "destroys the requested kanban" do 144 | kanban = Kanban.create! valid_attributes 145 | expect { 146 | delete :destroy, {:id => kanban.to_param}, valid_session 147 | }.to change(Kanban, :count).by(-1) 148 | end 149 | 150 | it "redirects to the top" do 151 | kanban = Kanban.create! valid_attributes 152 | delete :destroy, {:id => kanban.to_param}, valid_session 153 | expect(response).to redirect_to(root_url) 154 | end 155 | end 156 | 157 | end 158 | -------------------------------------------------------------------------------- /spec/controllers/sessions_controller_spec.rb: -------------------------------------------------------------------------------- 1 | describe SessionsController, type: :controller do 2 | describe "GET 'create'" do 3 | include_context :using_gitlab_mock 4 | 5 | subject{ post 'create', valid_params } 6 | 7 | before do 8 | json = < "email=#{login}&password=#{password}", 13 | :headers => {'Accept'=>'application/json'}). 14 | to_return(:status => 200, :body => json, :headers => {}) 15 | end 16 | 17 | let(:login) { "momozono_love" } 18 | let(:password){ "cure_peach" } 19 | let(:valid_params) { 20 | { 21 | login: login, 22 | password: password, 23 | } 24 | } 25 | 26 | describe "action behavior" do 27 | before do 28 | subject 29 | end 30 | 31 | it "returns http redirect" do 32 | expect(response).to be_redirect 33 | end 34 | 35 | it{ expect(response).to redirect_to root_path } 36 | 37 | it{ expect(session[:user_id]).not_to be_nil } 38 | end 39 | 40 | context "When not exists user" do 41 | before do 42 | subject 43 | end 44 | 45 | it{ expect(User.count).to eq 1 } 46 | 47 | let(:user){ User.first } 48 | it_behaves_like :a_user 49 | end 50 | 51 | context "When exists user" do 52 | before do 53 | @user = FactoryGirl.create(:momozono_love) 54 | subject 55 | end 56 | 57 | it{ expect(User.count).to eq 1 } 58 | 59 | let(:user){ @user } 60 | it_behaves_like :a_user 61 | end 62 | end 63 | 64 | describe "GET 'destroy'" do 65 | subject{ get 'destroy' } 66 | 67 | before do 68 | subject 69 | end 70 | 71 | it "returns http success" do 72 | expect(response).to be_redirect 73 | end 74 | 75 | it{ expect(response).to redirect_to root_path } 76 | 77 | it{ expect(session[:user_id]).to be_nil } 78 | end 79 | 80 | end 81 | -------------------------------------------------------------------------------- /spec/controllers/top_controller_spec.rb: -------------------------------------------------------------------------------- 1 | describe TopController, type: :controller do 2 | 3 | describe "GET 'index'" do 4 | it "returns http success" do 5 | get 'index' 6 | expect(response).to be_success 7 | end 8 | end 9 | 10 | end 11 | -------------------------------------------------------------------------------- /spec/factories/kanbans.rb: -------------------------------------------------------------------------------- 1 | # Read about factories at https://github.com/thoughtbot/factory_girl 2 | 3 | FactoryGirl.define do 4 | factory :kanban do 5 | gitlab_project_id 1 6 | name { generate :random_path } 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/factories/labels.rb: -------------------------------------------------------------------------------- 1 | # Read about factories at https://github.com/thoughtbot/factory_girl 2 | 3 | FactoryGirl.define do 4 | factory :label do 5 | kanban_id 1 6 | name "MyString" 7 | gitlab_label "MyString" 8 | is_backlog_issue false 9 | is_close_issue false 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/factories/sequences.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | def rand_str 3 | # via. http://d.hatena.ne.jp/JunMitani/20080214 4 | a = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a 5 | Array.new(10){a[rand(a.size)]}.join 6 | end 7 | 8 | sequence(:random_int) { rand(1 .. 100000) } 9 | sequence(:random_flag){ rand(10) < 5 } 10 | sequence(:random_str) { rand_str } 11 | sequence(:random_url) { "http://"+ rand_str } 12 | sequence(:random_mail){ "#{rand_str}@#{rand_str}.com" } 13 | 14 | sequence(:random_path) { rand_str + "/" + rand_str } 15 | 16 | sequence(:random_time){ 17 | diff_day = 150 - rand(365) 18 | Time.now + diff_day 19 | } 20 | end 21 | 22 | -------------------------------------------------------------------------------- /spec/factories/users.rb: -------------------------------------------------------------------------------- 1 | # Read about factories at https://github.com/thoughtbot/factory_girl 2 | 3 | FactoryGirl.define do 4 | factory :user do 5 | gitlab_user_id { generate :random_int } 6 | username { generate :random_str } 7 | email { generate :random_mail } 8 | private_token { generate :random_str } 9 | 10 | factory :momozono_love do 11 | gitlab_user_id 1 12 | username "momozono_love" 13 | email "cure_peach@fresh.precure.jp" 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/helpers/application_helper_spec.rb: -------------------------------------------------------------------------------- 1 | describe ApplicationHelper, type: :helper do 2 | 3 | # via. 4 | # * https://github.com/rails/rails/blob/v4.1.2/actionpack/CHANGELOG.md 5 | # * https://github.com/rails/rails/commit/8a067640e6fe222022dc77bb63d5da37ef75a189 6 | describe "Upgrade to Rails 4.1.4" do 7 | let(:slug){ "namespace/repository" } 8 | 9 | describe "url helper" do 10 | it "should not be escaped" do 11 | expect(kanban_path(slug)).to eq "/#{slug}" 12 | end 13 | end 14 | 15 | describe "#url_for" do 16 | it "should not be escaped" do 17 | expect(url_for(controller: :kanbans, action: :edit, id: slug)).to eq "/#{slug}/edit" 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/models/kanban_spec.rb: -------------------------------------------------------------------------------- 1 | describe Kanban do 2 | describe "#create_default_labels" do 3 | subject(:create_default_labels){ kanban.save! } 4 | 5 | let(:kanban){ FactoryGirl.build(:kanban) } 6 | 7 | it{ expect{ subject }.to change(kanban.labels, :count).by(4) } 8 | 9 | describe "1st label" do 10 | subject do 11 | create_default_labels 12 | kanban.labels[0] 13 | end 14 | 15 | its(:name) { should == "Backlog" } 16 | its(:gitlab_label) { should be nil } 17 | its(:disp_order) { should == 0 } 18 | its(:is_backlog_issue){ should be true } 19 | its(:is_close_issue) { should be false } 20 | end 21 | 22 | describe "2nd label" do 23 | subject do 24 | create_default_labels 25 | kanban.labels[1] 26 | end 27 | 28 | its(:name) { should == "Ready" } 29 | its(:gitlab_label) { should == "ready" } 30 | its(:disp_order) { should == 1 } 31 | its(:is_backlog_issue){ should be false } 32 | its(:is_close_issue) { should be false } 33 | end 34 | 35 | describe "3rd label" do 36 | subject do 37 | create_default_labels 38 | kanban.labels[2] 39 | end 40 | 41 | its(:name) { should == "In Progress" } 42 | its(:gitlab_label) { should == "in progress" } 43 | its(:disp_order) { should == 2 } 44 | its(:is_backlog_issue){ should be false } 45 | its(:is_close_issue) { should be false } 46 | end 47 | 48 | describe "4th label" do 49 | subject do 50 | create_default_labels 51 | kanban.labels[3] 52 | end 53 | 54 | its(:name) { should == "Done" } 55 | its(:gitlab_label) { should be nil } 56 | its(:disp_order) { should == 3 } 57 | its(:is_backlog_issue){ should be false } 58 | its(:is_close_issue) { should be true } 59 | end 60 | end 61 | 62 | describe "#issues_group_by_label" do 63 | before do 64 | @grouped_issue = kanban.issues_group_by_label(issues) 65 | end 66 | 67 | let(:kanban){ FactoryGirl.create(:kanban) } 68 | let(:issues){ 69 | [ 70 | # backlog 71 | Gitlab::ObjectifiedHash.new(id: 1 , state: "opened" , labels: nil), 72 | Gitlab::ObjectifiedHash.new(id: 2 , state: "reopened", labels: ["Bug", "Hotfix"]), 73 | 74 | # ready 75 | Gitlab::ObjectifiedHash.new(id: 3 , state: "opened" , labels: ["ready"]), 76 | 77 | # in progress 78 | Gitlab::ObjectifiedHash.new(id: 4 , state: "opened" , labels: ["in progress"]), 79 | Gitlab::ObjectifiedHash.new(id: 5 , state: "reopened", labels: ["in progress"]), 80 | Gitlab::ObjectifiedHash.new(id: 6 , state: "opened" , labels: ["Bug", "in progress"]), 81 | 82 | # done 83 | Gitlab::ObjectifiedHash.new(id: 7 , state: "closed" , labels: nil), 84 | Gitlab::ObjectifiedHash.new(id: 8 , state: "closed" , labels: ["ready"]), 85 | Gitlab::ObjectifiedHash.new(id: 9 , state: "closed" , labels: ["in progress"]), 86 | Gitlab::ObjectifiedHash.new(id: 10, state: "closed" , labels: ["Bug", "Hotfix"]), 87 | ] 88 | } 89 | let(:backlog_id) { kanban.labels.backlog.first.id } 90 | let(:ready_id) { kanban.labels.other.find_by(name: "Ready").id } 91 | let(:in_progress_id) { kanban.labels.other.find_by(name: "In Progress").id } 92 | let(:done_id) { kanban.labels.done.first.id } 93 | 94 | it{ expect(@grouped_issue.count).to eq kanban.labels.count } 95 | 96 | it "should return backlog issues" do 97 | expect(@grouped_issue[backlog_id]).to have(2).items 98 | end 99 | 100 | it "should return ready issues" do 101 | expect(@grouped_issue[ready_id]).to have(1).items 102 | end 103 | 104 | it "should return in progress issues" do 105 | expect(@grouped_issue[in_progress_id]).to have(3).items 106 | end 107 | 108 | it "should return done issues" do 109 | expect(@grouped_issue[done_id]).to have(4).items 110 | end 111 | end 112 | 113 | describe "#update_gitlab_issue_labels" do 114 | subject{ kanban.update_gitlab_issue_labels(gitlab_labels, @from_label.id, @to_label.id) } 115 | 116 | let(:kanban){ FactoryGirl.create(:kanban) } 117 | 118 | where(:gitlab_labels, :from_label_name, :to_label_name, :expected) do 119 | [ 120 | # backlog -> backlog 121 | [ ["bug", "high"] , "Backlog", "Backlog" , ["bug", "high"] ], 122 | # backlog -> other 123 | [ ["bug", "high"] , "Backlog", "In Progress", ["bug", "in progress", "high"] ], 124 | # backlog -> done 125 | [ ["bug", "high"] , "Backlog", "Done" , ["bug", "high"] ], 126 | # other -> backlog 127 | [ ["bug", "ready", "high"] , "Ready", "Backlog" , ["bug", "high"] ], 128 | # other -> other 129 | [ ["bug", "ready", "high"] , "Ready", "Ready" , ["bug", "ready", "high"] ], 130 | # other -> other2 131 | [ ["bug", "ready", "high"] , "Ready", "In Progress" , ["bug", "in progress", "high"] ], 132 | # other -> done 133 | [ ["bug", "ready", "high"] , "Ready", "Done" , ["bug", "high"] ], 134 | # done -> backlog 135 | [ ["bug", "high"] , "Done", "Backlog" , ["bug", "high"] ], 136 | # done -> other 137 | [ ["bug", "high"] , "Done", "Ready" , ["bug", "ready", "high"] ], 138 | # done -> done 139 | [ ["bug", "high"] , "Done", "Done" , ["bug", "high"] ], 140 | ] 141 | end 142 | 143 | with_them do 144 | before do 145 | @from_label = kanban.labels.find_by!(name: from_label_name) 146 | @to_label = kanban.labels.find_by!(name: to_label_name) 147 | end 148 | 149 | it{ should match_array expected } 150 | end 151 | 152 | end 153 | 154 | describe "#gitlab_issue_state" do 155 | subject{ kanban.gitlab_issue_state(@from_label.id, @to_label.id) } 156 | 157 | let(:kanban){ FactoryGirl.create(:kanban) } 158 | 159 | where(:from_label_name, :to_label_name, :expected) do 160 | [ 161 | # opened -> opened 162 | ["Ready", "In Progress", nil], 163 | # opened -> closed 164 | ["Ready", "Done" , "close"], 165 | # closed -> reopened 166 | ["Done" , "Ready" , "reopen"], 167 | # closed -> closed 168 | ["Done" , "Done" , nil], 169 | ] 170 | end 171 | 172 | with_them do 173 | before do 174 | @from_label = kanban.labels.find_by!(name: from_label_name) 175 | @to_label = kanban.labels.find_by!(name: to_label_name) 176 | end 177 | 178 | it{ should == expected } 179 | end 180 | 181 | end 182 | end 183 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rspec-parameterized' 3 | require 'webmock/rspec' 4 | 5 | require 'coveralls' 6 | Coveralls.wear! 7 | 8 | # This file is copied to spec/ when you run 'rails generate rspec:install' 9 | ENV["RAILS_ENV"] ||= 'test' 10 | require File.expand_path("../../config/environment", __FILE__) 11 | require 'rspec/rails' 12 | require 'rspec' 13 | 14 | # Requires supporting ruby files with custom matchers and macros, etc, in 15 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 16 | # run as spec files by default. This means that files in spec/support that end 17 | # in _spec.rb will both be required and run as specs, causing the specs to be 18 | # run twice. It is recommended that you do not name files matching this glob to 19 | # end with _spec.rb. You can configure this pattern with with the --pattern 20 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 21 | Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } 22 | 23 | # Checks for pending migrations before tests are run. 24 | # If you are not using ActiveRecord, you can remove this line. 25 | ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration) 26 | 27 | RSpec.configure do |config| 28 | # ## Mock Framework 29 | # 30 | # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line: 31 | # 32 | # config.mock_with :mocha 33 | # config.mock_with :flexmock 34 | # config.mock_with :rr 35 | 36 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 37 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 38 | 39 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 40 | # examples within a transaction, remove the following line or assign false 41 | # instead of true. 42 | config.use_transactional_fixtures = true 43 | 44 | # If true, the base class of anonymous controllers will be inferred 45 | # automatically. This will be the default behavior in future versions of 46 | # rspec-rails. 47 | config.infer_base_class_for_anonymous_controllers = false 48 | 49 | # Run specs in random order to surface order dependencies. If you find an 50 | # order dependency and want to debug it, you can fix the order by providing 51 | # the seed, which is printed after each run. 52 | # --seed 1234 53 | config.order = "random" 54 | 55 | # DatabaseRewinder 56 | config.before :suite do 57 | DatabaseRewinder.clean_all 58 | # or 59 | # DatabaseRewinder.clean_with :any_arg_that_would_be_actually_ignored_anyway 60 | end 61 | 62 | config.after :each do 63 | DatabaseRewinder.clean 64 | end 65 | end 66 | 67 | -------------------------------------------------------------------------------- /spec/support/shared_contexts/using_gitlab_mock.rb: -------------------------------------------------------------------------------- 1 | shared_context :using_gitlab_mock do 2 | before do 3 | Gitlab.configure do |config| 4 | config.endpoint = gitlab_endpoint 5 | config.private_token = gitlab_private_token 6 | end 7 | end 8 | 9 | let(:gitlab_endpoint) { "https://example.net/api/v3" } 10 | let(:gitlab_private_token){ "gitlab_private_token" } 11 | end 12 | -------------------------------------------------------------------------------- /spec/support/shared_examples/a_user.rb: -------------------------------------------------------------------------------- 1 | shared_examples_for :a_user do 2 | it{ expect(user.gitlab_user_id).not_to be_nil } 3 | it{ expect(user.username).not_to be_blank } 4 | it{ expect(user.private_token).not_to be_blank } 5 | it{ expect(user.email).not_to be_blank } 6 | end 7 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sue445/gitpeach/08096149c8d768c98159c9061cb324a4b2663be1/tmp/.keep -------------------------------------------------------------------------------- /vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sue445/gitpeach/08096149c8d768c98159c9061cb324a4b2663be1/vendor/assets/javascripts/.keep -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sue445/gitpeach/08096149c8d768c98159c9061cb324a4b2663be1/vendor/assets/stylesheets/.keep -------------------------------------------------------------------------------- /vendor/assets/stylesheets/1pxdeep/1pxdeep.less: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////// 2 | ///////////////////////////////////////////////// 3 | //// 4 | //// 1pxdeep v1.0 5 | //// 6 | //// Copyright 2013 Rex Riepe 7 | //// Licensed under the Apache License v2.0 8 | //// http://www.apache.org/licenses/LICENSE-2.0 9 | //// 10 | ///////////////////////////////////////////////// 11 | ///////////////////////////////////////////////// 12 | 13 | //// Bootstrap variable overrides 14 | 15 | @font-size-base: 16px; 16 | 17 | @border-radius-base: 6px; 18 | @border-radius-large: 8px; 19 | @border-radius-small: 4px; 20 | 21 | @list-group-bg: transparent; 22 | 23 | @table-border-color: transparent; 24 | 25 | @modal-backdrop-bg: @color1a; 26 | 27 | @input-height-base: (@line-height-computed + (@padding-base-vertical * 2) + 4); 28 | 29 | @btn-default-bg: @color1; 30 | @btn-default-border: @btn-default-bg; 31 | @btn-font-weight: normal; 32 | 33 | @brand-primary: hsl(hue(#428bca),@sat,@l-factor); 34 | @brand-info: hsl(hue(#5bc0de),@sat,@l-factor); 35 | @brand-success: hsl(hue(#5cb85c),(saturation(#5cb85c) + @sat)/2,(luma(#5cb85c) + @luma - 10)/2); 36 | @brand-warning: hsl(hue(#f0ad4e),(saturation(#f0ad4e) + @sat + 16)/2,(luma(#f0ad4e) + @luma - 10)/2); 37 | @brand-danger: hsl(hue(#d9534f),(saturation(#d9534f) + @sat)/2,(luma(#d9534f) + @luma + 10)/2); 38 | 39 | @navbar-link-color: white; 40 | @navbar-link-hover-color: white; 41 | @navbar-link-hover-bg: transparent; 42 | @navbar-link-active-color: white; 43 | @navbar-link-active-bg: @1pxdeep-bg; 44 | @navbar-link-disabled-color: #ccc; 45 | @navbar-link-disabled-bg: transparent; 46 | 47 | //// SchemeLESS variable overrides 48 | 49 | @luma-upper-break:72; 50 | 51 | @contrast:@luma/50; 52 | 53 | //// Theme variables 54 | 55 | @1pxdeep-default-button-color: @color1b; 56 | @1pxdeep-bg: @color1; 57 | @body-bg: @1pxdeep-bg; 58 | 59 | 60 | //// Mix-ins 61 | 62 | .button-text (@color) when (luma(@color1) >= @luma-upper-break) { 63 | color:average(darken(@color,30%),#222); 64 | } 65 | 66 | .button-text (@color) when (luma(@color1) < @luma-upper-break) { 67 | color:#ffffff; 68 | } 69 | 70 | .contrast (@color) when (luma(@color) >= @luma-upper-break) { 71 | color:mix(darken(@color,18%),#111,50%); 72 | } 73 | 74 | .contrast (@color) when (luma(@color) < @luma-upper-break) { 75 | color:hsl(hue(@color),@sat,97%); 76 | } 77 | 78 | .contrast-dim (@color) when (luma(@color) >= @luma-upper-break) { 79 | color:mix(darken(@color,5%),#111,65%); 80 | } 81 | 82 | .contrast-dim (@color) when (luma(@color) < @luma-upper-break) { 83 | color:mix(@color,#ffffff,25%); 84 | } 85 | 86 | .contrast-link (@color) when (luma(@color) >= @luma-upper-break) { 87 | color:average(darken(@color,80%),#222); 88 | } 89 | 90 | .contrast-link (@color) when (luma(@color) < @luma-upper-break) { 91 | color:mix(#ffffff,@color1,75%); 92 | } 93 | 94 | .btn-borders(@color) { 95 | border:1px solid transparent; 96 | border-top-color:mix(softlight(white,@color),@color,30%); 97 | } 98 | 99 | .btn-borders(@color) when (luma(@seed-color) > @luma-upper-break) { 100 | border:1px solid transparent; 101 | border-bottom-color:mix(#333,@color,30%); 102 | } 103 | 104 | .btn-borders(@color) when (luma(@seed-color) < 10%) { 105 | border:1px solid transparent; 106 | border-top-color:mix(softlight(white,@color),@color,30%); 107 | } 108 | 109 | .btn-pseudo-states(@color, @background, @border) { // overwrites bootstrap mix-in 110 | .contrast(@background); 111 | .btn-borders(@background); 112 | 113 | &:hover, 114 | &:focus, 115 | &:active, 116 | &.active { 117 | background-color: darken(@background, 2%); 118 | .btn-borders(darken(@background, 2%)); 119 | border-top:1px solid darken(@background, 2%); 120 | .1pxdeep-shadow(1.5); 121 | } 122 | 123 | &.disabled, 124 | &[disabled], 125 | fieldset[disabled] & { 126 | &, 127 | &:hover, 128 | &:focus, 129 | &:active, 130 | &.active { 131 | background-color: @background; 132 | border-color:transparent; 133 | } 134 | } 135 | } 136 | 137 | //shadows 138 | 139 | @1pxdeep-shadow-intensity:.1; 140 | 141 | @1pxdeep-shadow-color:mix(darken(@color1,33%),#222,50%); 142 | 143 | @1pxdeep-shadow-r:red(@1pxdeep-shadow-color); 144 | @1pxdeep-shadow-g:green(@1pxdeep-shadow-color); 145 | @1pxdeep-shadow-b:blue(@1pxdeep-shadow-color); 146 | 147 | .1pxdeep-shadow(@intensity:1) { 148 | .box-shadow(0px 1px 2px rgba(@1pxdeep-shadow-r,@1pxdeep-shadow-g,@1pxdeep-shadow-b,@intensity*@1pxdeep-shadow-intensity)); 149 | //.box-shadow(none); 150 | } 151 | 152 | .1pxdeep-shadow(@intensity:1) when (luma(@color1) > @luma-upper-break) { 153 | @1pxdeep-shadow-intensity:.05; 154 | .box-shadow(0px 1px 2px rgba(@1pxdeep-shadow-r,@1pxdeep-shadow-g,@1pxdeep-shadow-b,@intensity*@1pxdeep-shadow-intensity)); 155 | //.box-shadow(none); 156 | } 157 | 158 | .1pxdeep-shadow-inset(@intensity:1) { 159 | .box-shadow(inset 0px 0px 3px rgba(@1pxdeep-shadow-r,@1pxdeep-shadow-g,@1pxdeep-shadow-b,@intensity*@1pxdeep-shadow-intensity)); 160 | //.box-shadow(none); 161 | } 162 | 163 | .1pxdeep-shadow-inset(@intensity:1) when (luma(@color1) > @luma-upper-break) { 164 | @1pxdeep-shadow-intensity:.05; 165 | .box-shadow(inset 0px 0px 3px rgba(@1pxdeep-shadow-r,@1pxdeep-shadow-g,@1pxdeep-shadow-b,@intensity*@1pxdeep-shadow-intensity)); 166 | //.box-shadow(none); 167 | } 168 | 169 | 170 | .btn { 171 | .1pxdeep-shadow(); 172 | 173 | &:active, 174 | &.active { 175 | .box-shadow(none); 176 | } 177 | 178 | &.disabled, 179 | &[disabled], 180 | fieldset[disabled] & { 181 | .opacity(.65); 182 | .box-shadow(none); 183 | } 184 | 185 | .btn-color(@color) { 186 | background:@color; 187 | .contrast(@color); 188 | .btn-borders(@color); 189 | 190 | &:hover, 191 | &:focus { 192 | background:lighten(@color,2%); 193 | .btn-borders(lighten(@color,2%)); 194 | .contrast(@color); 195 | } 196 | 197 | &:active, 198 | &.active { 199 | .contrast-dim(@color); 200 | background:darken(@color,2%); 201 | border:1px solid darken(@color,2%); 202 | } 203 | } 204 | 205 | .btn-color(@color) when (luma(@color) > @luma-upper-break) { 206 | background:@color; 207 | .contrast(@color); 208 | .btn-borders(@color); 209 | 210 | &:hover, 211 | &:focus { 212 | background:lighten(@color,2%); 213 | .contrast(@color); 214 | } 215 | 216 | &:active, 217 | &.active { 218 | background:darken(@color,2%); 219 | border:1px solid darken(@color,2%); 220 | } 221 | } 222 | 223 | .btn-color(@1pxdeep-default-button-color); 224 | &.color1 {.btn-color(@color1);} 225 | &.color1a {.btn-color(@color1a);} 226 | &.color1b {.btn-color(@color1b);} 227 | &.color1c {.btn-color(@color1c);} 228 | &.color2 {.btn-color(@color2);} 229 | &.color2a {.btn-color(@color2a);} 230 | &.color2b {.btn-color(@color2b);} 231 | &.color2c {.btn-color(@color2c);} 232 | &.color3 {.btn-color(@color3);} 233 | &.color3a {.btn-color(@color3a);} 234 | &.color3b {.btn-color(@color3b);} 235 | &.color3c {.btn-color(@color3c);} 236 | &.color4 {.btn-color(@color4);} 237 | &.color4a {.btn-color(@color4a);} 238 | &.color4b {.btn-color(@color4b);} 239 | &.color4c {.btn-color(@color4c);} 240 | 241 | &.btn-primary {.btn-color(@brand-primary);} 242 | &.btn-info {.btn-color(@brand-info);} 243 | &.btn-success {.btn-color(@brand-success);} 244 | &.btn-warning {.btn-color(@brand-warning);} 245 | &.btn-danger {.btn-color(@brand-danger);} 246 | &.btn-inverse {.btn-color(desaturate(@color1a,100%));} 247 | } 248 | 249 | .schemify(@color) { 250 | @mix-weight:60%; 251 | @new-color:mix(hsl(hue(@color),@sat,@luma),@color,@mix-weight); 252 | background:@new-color; 253 | .contrast(@new-color); 254 | } 255 | 256 | 257 | //elements 258 | 259 | body,html { 260 | background:@1pxdeep-bg; 261 | .contrast(@1pxdeep-bg); 262 | 263 | -webkit-font-smoothing: antialiased; 264 | -moz-font-smoothing: antialiased; 265 | font-smoothing: antialiased; 266 | } 267 | 268 | a { 269 | .contrast-link(@1pxdeep-bg); 270 | 271 | &:hover { 272 | .contrast-link(@1pxdeep-bg); 273 | } 274 | } 275 | 276 | blockquote { 277 | 278 | padding:6px 8px; 279 | 280 | cite { 281 | font-style:italic; 282 | } 283 | 284 | .blockquote-color(@color,@alt-color) { 285 | background:@color; 286 | border-left:4px solid @alt-color; 287 | .contrast(@color); 288 | 289 | &.pull-right { 290 | border-left:none; 291 | border-right:4px solid @alt-color; 292 | } 293 | 294 | small { 295 | .contrast(@color); 296 | text-shadow:none; 297 | } 298 | } 299 | 300 | .blockquote-color(@color1c,@color1a); 301 | &.color1 { .blockquote-color(@color1c,@color1a)} 302 | &.color2 { .blockquote-color(@color2c,@color2a)} 303 | &.color3 { .blockquote-color(@color3c,@color3a)} 304 | &.color4 { .blockquote-color(@color4c,@color4a)} 305 | } 306 | 307 | /*input[type="checkbox"] { //experimental flat look for checkboxes 308 | margin-right:13px; 309 | margin-top:5px; 310 | background:white; 311 | 312 | &:before { 313 | display:block; 314 | content:""; 315 | width:22px; 316 | height:22px; 317 | //border:2px solid @1pxdeep-bg; 318 | border-radius:@border-radius-base; 319 | background:white; 320 | position:relative; 321 | top:-4px; 322 | left:-1px; 323 | .box-shadow(inset 1px 1px 0px @1pxdeep-bg); 324 | } 325 | 326 | &:checked { 327 | &:after { 328 | display:block; 329 | content:""; 330 | width:8px; 331 | height:13px; 332 | border-radius:0; 333 | background:white; 334 | position:relative; 335 | top:-23px; 336 | left:7px; 337 | .box-shadow(inset -3px -3px 0px @1pxdeep-bg); 338 | border:none; 339 | border-radius:0; 340 | .rotate(45deg); 341 | } 342 | } 343 | }*/ 344 | 345 | .form-control { 346 | .box-shadow(none); 347 | } 348 | 349 | // small fix in headlines 350 | 351 | h1, h2, h3, h4, h5, h6, 352 | .h1, .h2, .h3, .h4, .h5, .h6 { 353 | small { 354 | color:inherit; 355 | } 356 | } 357 | 358 | //dropdown 359 | 360 | .dropdown-menu { 361 | .box-shadow(none); 362 | border:none; 363 | 364 | li { 365 | border:none; 366 | } 367 | 368 | .dropdown-menu-color(@color) { 369 | li { 370 | &:hover { 371 | a { 372 | background:@color; 373 | .contrast(@color); 374 | } 375 | 376 | } 377 | } 378 | } 379 | 380 | .dropdown-menu-color(@color1b); 381 | 382 | &.color1 {.dropdown-menu-color(@color1b);} 383 | &.color2 {.dropdown-menu-color(@color2b);} 384 | &.color3 {.dropdown-menu-color(@color3b);} 385 | &.color4 {.dropdown-menu-color(@color4b);} 386 | 387 | } 388 | 389 | .btn-group.open .dropdown-toggle { 390 | .box-shadow(none); 391 | } 392 | 393 | .dropup .caret, .navbar-fixed-bottom .dropdown .caret { 394 | border-top:none; 395 | border-bottom-color:white; 396 | } 397 | 398 | .btn-default .caret { 399 | border-top-color:white; 400 | } 401 | 402 | //well 403 | 404 | .well { 405 | border:none; 406 | 407 | .well-color(@color) { 408 | .1pxdeep-shadow-inset(2.5); 409 | background:@color; 410 | .contrast(@color); 411 | 412 | a { 413 | .contrast-link(@color); 414 | } 415 | } 416 | 417 | .well-color(@modal-backdrop-bg); 418 | } 419 | 420 | //navbar 421 | 422 | .navbar { 423 | 424 | border:none; 425 | 426 | .navbar-color(@color) { 427 | background:@color; 428 | .contrast(@color); 429 | } 430 | 431 | .navbar-color(@color1a); 432 | 433 | &.color1 { .navbar-color(@color1a);} 434 | &.color2 { .navbar-color(@color2a);} 435 | &.color3 { .navbar-color(@color3a);} 436 | &.color4 { .navbar-color(@color4a);} 437 | } 438 | 439 | .navbar-brand { 440 | color:inherit; 441 | } 442 | 443 | .navbar-nav { 444 | 445 | .active { 446 | overflow-y:hidden; 447 | } 448 | 449 | .navbar-nav-color(@color) { 450 | .active > a { 451 | .1pxdeep-shadow(5); 452 | .contrast(@color); 453 | background-color: @color; 454 | 455 | &:hover { 456 | .contrast(@color); 457 | background-color: @color; 458 | } 459 | } 460 | 461 | li > a { 462 | .contrast-dim(darken(@color,10%)); 463 | line-height: 25px; 464 | z-index:100; 465 | 466 | &:hover, 467 | &:focus { 468 | @ncolor:lighten(@color,4%); 469 | .contrast(@ncolor); 470 | background-color: @ncolor; 471 | } 472 | } 473 | } 474 | 475 | .navbar-nav-color(@1pxdeep-bg); 476 | 477 | &.color1 { .navbar-nav-color(@color1);} 478 | &.color2 { .navbar-nav-color(@color2);} 479 | &.color3 { .navbar-nav-color(@color3);} 480 | &.color4 { .navbar-nav-color(@color4);} 481 | } 482 | 483 | .navbar-fixed-top, .navbar-fixed-bottom { 484 | .1pxdeep-shadow-inset(2); 485 | 486 | .navbar-inner { 487 | //.box-shadow(none); 488 | padding:0; 489 | //margin-bottom:12px; 490 | } 491 | } 492 | 493 | .navbar-toggle { 494 | @color:@color1; 495 | background-color: @color; 496 | .btn-borders(@color); 497 | 498 | &:hover, 499 | &:focus { 500 | background-color: lighten(@color,2%); 501 | } 502 | 503 | &:active, 504 | .active { 505 | background-color:darken(@color,2%); 506 | border-top-color:darken(@color,2%); 507 | } 508 | 509 | .icon-bar-color(@color) when (luma(@color) > @luma-upper-break) { 510 | background-color:#333; 511 | } 512 | 513 | .icon-bar-color(@color) when (luma(@color) =< @luma-upper-break) { 514 | background-color:white; 515 | } 516 | 517 | .icon-bar { 518 | .icon-bar-color(@color); 519 | } 520 | } 521 | 522 | .navbar-collapse { 523 | border-top:none; 524 | } 525 | 526 | //pills 527 | 528 | .nav-pills { 529 | text-shadow:none; 530 | color:black; 531 | 532 | .pills-color(@color,@active-color) { 533 | > li > a { 534 | &:hover { 535 | background:lighten(@color,10%); 536 | .contrast(lighten(@color,10%)); 537 | } 538 | } 539 | 540 | > li.active > a { 541 | background:@active-color; 542 | .contrast(@active-color); 543 | 544 | &:hover { 545 | background:lighten(@active-color,2%); 546 | .contrast(lighten(@active-color,2%)); 547 | } 548 | } 549 | } 550 | 551 | .pills-color(@color1,@color1c); 552 | 553 | &.color1 { .pills-color(@color1,@color1c);} 554 | &.color2 { .pills-color(@color2,@color2c);} 555 | &.color3 { .pills-color(@color3,@color2c);} 556 | &.color4 { .pills-color(@color4,@color2c);} 557 | } 558 | 559 | //alert 560 | 561 | .alert { 562 | border:none; 563 | text-shadow:none; 564 | 565 | .close { 566 | .opacity(100); 567 | text-shadow:none; 568 | margin-top:-1px; 569 | } 570 | 571 | .alert-color(@color) { 572 | background:@color; 573 | .contrast(@color); 574 | 575 | .close { 576 | .contrast(@color); 577 | } 578 | } 579 | 580 | .alert-color-schemify(@color) { 581 | .alert-color(@color); 582 | .schemify(@color); 583 | 584 | .close { 585 | .contrast(mix(hsl(hue(@color),@sat,@luma),@color,60%);); 586 | } 587 | } 588 | 589 | .alert-color-schemify(#fbfcd6); 590 | 591 | &.color1 { .alert-color(@color1);} 592 | &.color2 { .alert-color(@color2);} 593 | &.color3 { .alert-color(@color3);} 594 | &.color4 { .alert-color(@color4);} 595 | 596 | &.alert-error,&.alert-danger {.alert-color-schemify(#ee5f5b);} 597 | &.alert-info {.alert-color-schemify(#08c);} 598 | &.alert-success {.alert-color-schemify(#62c462);} 599 | } 600 | 601 | //progress bars 602 | 603 | .progress { 604 | background:darken(@1pxdeep-bg,5%); 605 | .1pxdeep-shadow-inset(2); 606 | 607 | .progress-color(@color) { 608 | background:darken(@color,5%); 609 | } 610 | 611 | &.color1 { .progress-color(@color1);} 612 | &.color2 { .progress-color(@color2);} 613 | &.color3 { .progress-color(@color3);} 614 | &.color4 { .progress-color(@color4);} 615 | } 616 | 617 | .progress-bar { 618 | .box-shadow(none); 619 | 620 | .progress-bar-color(@color) { 621 | background:@color; 622 | .contrast(@color); 623 | } 624 | 625 | .progress-bar-color(@color1c); 626 | &.color1 { .progress-bar-color(@color1c);} 627 | &.color2 { .progress-bar-color(@color2c);} 628 | &.color3 { .progress-bar-color(@color3c);} 629 | &.color4 { .progress-bar-color(@color4c);} 630 | &.progress-bar-success { .progress-bar-color(@brand-success);} 631 | &.progress-bar-warning { .progress-bar-color(@brand-warning);} 632 | &.progress-bar-danger { .progress-bar-color(@brand-danger);} 633 | &.progress-bar-info { .progress-bar-color(@brand-info);} 634 | } 635 | 636 | //accordion 637 | 638 | .accordion { 639 | .accordion-heading { 640 | border-bottom: 0; 641 | border-radius:6px; 642 | a { 643 | &:hover { 644 | text-decoration:none; 645 | } 646 | } 647 | } 648 | 649 | .accordion-group { 650 | border:none; 651 | } 652 | 653 | .accordion-inner { 654 | margin:12px; 655 | padding:15px; 656 | border-radius:6px; 657 | } 658 | 659 | .accordion-inner-color(@color) { 660 | .accordion-inner { 661 | background:@color; 662 | .contrast(@color); 663 | //text-shadow:none; 664 | } 665 | } 666 | 667 | .accordion-heading-color(@color) { 668 | .accordion-heading { 669 | background:@color; 670 | .contrast(@color); 671 | .btn-borders(@color); 672 | 673 | a { 674 | .contrast(@color); 675 | } 676 | } 677 | } 678 | 679 | .accordion-heading-color(@color1b); 680 | .accordion-inner-color(@color1c); 681 | 682 | &.color1 { .accordion-heading-color(@color1b); 683 | .accordion-inner-color(@color1c);} 684 | &.color2 { .accordion-heading-color(@color2b); 685 | .accordion-inner-color(@color2c);} 686 | &.color3 { .accordion-heading-color(@color3b); 687 | .accordion-inner-color(@color3c);} 688 | &.color4 { .accordion-heading-color(@color4b); 689 | .accordion-inner-color(@color4c);} 690 | } 691 | 692 | //jumbotron 693 | 694 | .jumbotron { 695 | font-size: 24px; 696 | line-height:32px; 697 | font-weight: normal; 698 | 699 | .jumbotron-color(@color) { 700 | background:@color; 701 | .contrast(@color); 702 | } 703 | 704 | .jumbotron-color(@color1b); 705 | 706 | &.color1 {.jumbotron-color(@color1b);} 707 | &.color2 {.jumbotron-color(@color2b);} 708 | &.color3 {.jumbotron-color(@color3b);} 709 | &.color4 {.jumbotron-color(@color4b);} 710 | 711 | h1 { 712 | margin-bottom: 12px; 713 | font-size: 60px; 714 | line-height: 1; 715 | color: @jumbotron-heading-color; 716 | letter-spacing: -1px; 717 | } 718 | } 719 | 720 | //breadcrumbs 721 | 722 | .breadcrumb { 723 | 724 | > .active { 725 | font-weight:bold; 726 | } 727 | 728 | .breadcrumb-color(@color) { 729 | background:@color; 730 | li { 731 | .contrast(@color); 732 | 733 | > a { 734 | .contrast(@color); 735 | } 736 | } 737 | 738 | > .active { 739 | .contrast(@color) 740 | } 741 | 742 | } 743 | 744 | .breadcrumb-color(@color1b); 745 | 746 | &.color1 { .breadcrumb-color(@color1b);} 747 | &.color2 { .breadcrumb-color(@color2b);} 748 | &.color3 { .breadcrumb-color(@color3b);} 749 | &.color4 { .breadcrumb-color(@color4b);} 750 | 751 | 752 | } 753 | 754 | //pager 755 | 756 | .pager { 757 | > li { 758 | > a { 759 | border:none; 760 | } 761 | } 762 | 763 | .pager-color(@color) { 764 | > li { 765 | > a { 766 | background:@color; 767 | .contrast(@color); 768 | } 769 | 770 | > a:hover, 771 | > a:focus { 772 | background-color: lighten(@color,2%); 773 | } 774 | } 775 | 776 | > .disabled { 777 | > a { 778 | background:@color; 779 | .contrast-dim(@color); 780 | .opacity(.5); 781 | } 782 | 783 | > a:hover, 784 | > a:focus { 785 | background-color:@color; 786 | .contrast-dim(@color); 787 | } 788 | 789 | } 790 | } 791 | 792 | .pager-color(@color1b); 793 | 794 | &.color1 { .pager-color(@color1b);} 795 | &.color2 { .pager-color(@color2b);} 796 | &.color3 { .pager-color(@color3b);} 797 | &.color4 { .pager-color(@color4b);} 798 | 799 | &.disabled { .pager-color(#ddd);} 800 | } 801 | 802 | //pagination 803 | 804 | .pagination { 805 | background:none; 806 | 807 | .page-color(@color,@active-color) { 808 | 809 | > li > a { 810 | background:@color; 811 | .contrast(@color); 812 | .btn-borders(@color); 813 | } 814 | 815 | > li > a:hover, 816 | > li > a:focus, 817 | > li > a:active, 818 | > .active > a, 819 | > .active > span { 820 | .contrast(lighten(@color,2%)); 821 | background-color:lighten(@color,2%); 822 | } 823 | > .active > a, 824 | > .active > span { 825 | .contrast(@active-color); 826 | background-color:lighten(@active-color,2%); 827 | } 828 | } 829 | 830 | .page-color(@color1b,@color1c); 831 | 832 | &.color1 {.page-color(@color1b,@color1c);} 833 | &.color2 {.page-color(@color2b,@color2c);} 834 | &.color3 {.page-color(@color3b,@color3c);} 835 | &.color4 {.page-color(@color4b,@color4c);} 836 | 837 | > .disabled { 838 | > span, 839 | > a, 840 | > a:hover, 841 | > a:focus { 842 | color:inherit; 843 | background-color:inherit; 844 | .opacity(.5); 845 | } 846 | } 847 | } 848 | 849 | //thumbnail 850 | 851 | .thumbnail, 852 | .img-thumbnail { 853 | border: 1px solid transparent; 854 | 855 | .thumbnail-color(@color) { 856 | background-color:@color; 857 | 858 | .caption { 859 | .contrast(@color); 860 | } 861 | } 862 | 863 | .thumbnail-color(@color1b); 864 | 865 | &.color1 {.thumbnail-color(@color1b);} 866 | &.color2 {.thumbnail-color(@color2b);} 867 | &.color3 {.thumbnail-color(@color3b);} 868 | &.color4 {.thumbnail-color(@color4b);} 869 | } 870 | 871 | //form 872 | 873 | .form-horizontal { 874 | font-size:18px; 875 | } 876 | 877 | legend { 878 | .contrast(@1pxdeep-bg); 879 | } 880 | 881 | .help-block { 882 | .contrast(@1pxdeep-bg); 883 | } 884 | 885 | .form-color(@color) { 886 | legend { 887 | .contrast(@color); 888 | } 889 | 890 | .help-block { 891 | .contrast(@color); 892 | } 893 | } 894 | 895 | .color1 { 896 | .form-color(@color1); 897 | } 898 | 899 | //input group 900 | 901 | .input-group { 902 | position: relative; // For dropdowns 903 | display: table; 904 | border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table 905 | 906 | // Undo padding and float of grid classes 907 | &.col { 908 | float: none; 909 | padding-left: 0; 910 | padding-right: 0; 911 | } 912 | 913 | .form-control { 914 | width: 100%; 915 | margin-bottom: 0; 916 | } 917 | } 918 | 919 | .input-group-addon { 920 | border: none; 921 | border-radius: @border-radius-base; 922 | 923 | .input-group-addon-color(@color) { 924 | background:@color; 925 | .contrast(@color); 926 | } 927 | 928 | .input-group-addon-color(@color1b); 929 | 930 | &.color1 {.input-group-addon-color(@color1b)} 931 | 932 | } 933 | 934 | //code 935 | 936 | code, pre { 937 | font-size:14px; 938 | text-shadow:none; 939 | 940 | .code-color(@color) { 941 | background:@color; 942 | .contrast(@color); 943 | border-color:@color; 944 | } 945 | 946 | .code-color(@color1c); 947 | 948 | &.color1 { .code-color(@color1b)} 949 | &.color2 { .code-color(@color2b)} 950 | &.color3 { .code-color(@color3b)} 951 | &.color4 { .code-color(@color4b)} 952 | } 953 | 954 | //labels and badges 955 | 956 | .label, .badge { 957 | .label-color(@color) { 958 | background:@color; 959 | .contrast(@color); 960 | text-shadow:none; 961 | } 962 | 963 | .label-color(@color1a); 964 | 965 | &.color1 { .label-color(@color1);} 966 | &.color2 { .label-color(@color2);} 967 | &.color3 { .label-color(@color3);} 968 | &.color4 { .label-color(@color4);} 969 | 970 | &.label-success { .label-color(@brand-success);} 971 | &.label-info { .label-color(@brand-info);} 972 | &.label-warning { .label-color(@brand-warning);} 973 | &.label-danger { .label-color(@brand-danger);} 974 | } 975 | 976 | //nav tabs 977 | 978 | .nav-tabs { 979 | > li { 980 | > a { 981 | border: none; 982 | 983 | &:hover { 984 | border:none; 985 | } 986 | } 987 | 988 | &.active > a { 989 | &, 990 | &:hover, 991 | &:focus { 992 | border:none; 993 | } 994 | } 995 | } 996 | 997 | .nav-tabs-color(@color) { 998 | border-bottom:2px solid @color; 999 | 1000 | > li { 1001 | 1002 | > a { 1003 | &:hover { 1004 | background:lighten(@color,2%); 1005 | } 1006 | } 1007 | 1008 | &.active > a { 1009 | &, 1010 | &:hover, 1011 | &:focus { 1012 | .contrast(@color); 1013 | background-color:@color; 1014 | } 1015 | } 1016 | } 1017 | } 1018 | 1019 | .nav-tabs-color(@color1c); 1020 | 1021 | &.color1 { .nav-tabs-color(@color1a);} 1022 | &.color2 { .nav-tabs-color(@color2a);} 1023 | &.color3 { .nav-tabs-color(@color3a);} 1024 | &.color4 { .nav-tabs-color(@color4a);} 1025 | 1026 | 1027 | } 1028 | 1029 | //table 1030 | 1031 | table { 1032 | max-width: 100%; 1033 | text-shadow:none; 1034 | border-collapse: collapse; 1035 | border-spacing: 0; 1036 | border-color:transparent; 1037 | border-radius:@border-radius-base; 1038 | 1039 | thead, 1040 | tbody, 1041 | tfoot { 1042 | > tr { 1043 | > th, 1044 | > td { 1045 | border-top:none; 1046 | } 1047 | } 1048 | } 1049 | 1050 | tbody + tbody { 1051 | border-top: none; 1052 | } 1053 | 1054 | .table-color(@color) { 1055 | background-color:@color; 1056 | .contrast(@color); 1057 | 1058 | &.table { 1059 | th, 1060 | td { 1061 | border-top-color: @color; 1062 | } 1063 | tbody + tbody { 1064 | border-top-color:@color; 1065 | } 1066 | } 1067 | 1068 | &.table-bordered { 1069 | border:none; 1070 | } 1071 | 1072 | &.table-striped { 1073 | .table-striped-color(darken(@color,5%)); 1074 | } 1075 | 1076 | .table-striped-color(@color) { 1077 | tbody { 1078 | > tr:nth-child(odd) > td, 1079 | > tr:nth-child(odd) > th { 1080 | background-color:darken(@color,1%); 1081 | } 1082 | } 1083 | } 1084 | 1085 | .table-striped-color (@color) when (luma(@color) > @luma-upper-break) { 1086 | tbody { 1087 | > tr:nth-child(odd) > td, 1088 | > tr:nth-child(odd) > th { 1089 | background-color:darken(@color,1%); 1090 | } 1091 | } 1092 | } 1093 | 1094 | .table-striped-color (@color) when (luma(@color) < @luma-lower-break) { 1095 | tbody { 1096 | > tr:nth-child(odd) > td, 1097 | > tr:nth-child(odd) > th { 1098 | background-color:lighten(@color,1%); 1099 | } 1100 | } 1101 | } 1102 | 1103 | &.table-hover { 1104 | tbody { 1105 | tr:hover > td, 1106 | tr:hover > th { 1107 | background-color: lighten(@color,5%); 1108 | } 1109 | } 1110 | } 1111 | } 1112 | 1113 | .table-color(@color1b); 1114 | 1115 | &.color1 { .table-color(@color1b);} 1116 | &.color2 { .table-color(@color2b);} 1117 | &.color3 { .table-color(@color3b);} 1118 | &.color4 { .table-color(@color4b);} 1119 | } 1120 | 1121 | //hr 1122 | 1123 | hr { 1124 | .hr-color(@color) { 1125 | background:@color; 1126 | color:@color; 1127 | border-color:@color; 1128 | } 1129 | 1130 | .hr-color(color1c); 1131 | &.color1 {.hr-color(@color1c);} 1132 | &.color2 {.hr-color(@color2c);} 1133 | &.color3 {.hr-color(@color3c);} 1134 | &.color4 {.hr-color(@color4c);} 1135 | } 1136 | 1137 | //page header 1138 | 1139 | .page-header { 1140 | border-width:2px; 1141 | 1142 | .page-header-color(@color) { 1143 | border-color:@color; 1144 | .contrast(@1pxdeep-bg); 1145 | 1146 | small { 1147 | .contrast(@1pxdeep-bg); 1148 | } 1149 | } 1150 | 1151 | .page-header-color(@color1c); 1152 | &.color1 {.page-header-color(@color1c);} 1153 | &.color2 {.page-header-color(@color2c);} 1154 | &.color3 {.page-header-color(@color3c);} 1155 | &.color4 {.page-header-color(@color4c);} 1156 | } 1157 | 1158 | //modal 1159 | 1160 | .modal-dialog { 1161 | padding-right:0; 1162 | padding-left:0; 1163 | width:100%; 1164 | } 1165 | 1166 | .modal-content { 1167 | .box-shadow(none); 1168 | border:none; 1169 | background:none; 1170 | width:100%; 1171 | } 1172 | 1173 | .modal-header { 1174 | border:none; 1175 | width:60%; 1176 | margin-left:20%; 1177 | padding-left:0; 1178 | padding-right:0; 1179 | 1180 | > .close { 1181 | .contrast(@modal-backdrop-bg); 1182 | margin-top:0; 1183 | .opacity(1); 1184 | padding:14px; //more touch-friendly 1185 | position:relative; 1186 | top:-12px; 1187 | right:-14px; 1188 | } 1189 | } 1190 | 1191 | .modal-title { 1192 | .contrast(@modal-backdrop-bg); 1193 | //text-shadow:none; 1194 | margin-left:0; 1195 | } 1196 | 1197 | .modal-body { 1198 | border-radius:0; 1199 | width:100%; 1200 | padding-left:20%; 1201 | padding-right:20%; 1202 | margin:0; 1203 | 1204 | .modal-body-color(@color) { 1205 | background:@color; 1206 | .contrast(@color); 1207 | } 1208 | 1209 | .modal-body-color(@color1); 1210 | 1211 | &.color1 { .modal-body-color(@color1);} 1212 | &.color1a { .modal-body-color(@color1a);} 1213 | &.color1b { .modal-body-color(@color1b);} 1214 | &.color1c { .modal-body-color(@color1c);} 1215 | &.color2 { .modal-body-color(@color2);} 1216 | &.color2a { .modal-body-color(@color2a);} 1217 | &.color2b { .modal-body-color(@color2b);} 1218 | &.color2c { .modal-body-color(@color2c);} 1219 | &.color3 { .modal-body-color(@color3);} 1220 | &.color3a { .modal-body-color(@color3a);} 1221 | &.color3b { .modal-body-color(@color3b);} 1222 | &.color3c { .modal-body-color(@color3c);} 1223 | &.color4 { .modal-body-color(@color4);} 1224 | &.color4a { .modal-body-color(@color4a);} 1225 | &.color4b { .modal-body-color(@color4b);} 1226 | &.color4c { .modal-body-color(@color4c);} 1227 | } 1228 | 1229 | .modal-footer { 1230 | background:transparent; 1231 | margin-top:0; 1232 | border:none; 1233 | width:60%; 1234 | margin-left:20%; 1235 | padding-left:0; 1236 | padding-right:0; 1237 | } 1238 | 1239 | .modal-backdrop { 1240 | background-color: @modal-backdrop-bg; 1241 | &.fade { .opacity(0); } 1242 | &.in { .opacity(1); } 1243 | 1244 | &:before { 1245 | display:block; 1246 | content:""; 1247 | background:@modal-backdrop-bg; 1248 | width:100%; 1249 | height:50%; 1250 | position:absolute; 1251 | top:0; 1252 | left:0; 1253 | } 1254 | } 1255 | 1256 | .close { 1257 | text-shadow:none; 1258 | } 1259 | 1260 | @media screen and (max-width: @screen-tablet) { 1261 | 1262 | .modal-header, .modal-footer { 1263 | margin-left:4%; 1264 | margin-right:4%; 1265 | width:92%; 1266 | } 1267 | 1268 | .modal-body { 1269 | padding-left:4%; 1270 | padding-right:4%; 1271 | } 1272 | 1273 | } 1274 | 1275 | //panel 1276 | .panel { 1277 | border:none; 1278 | .box-shadow(none); 1279 | 1280 | .panel-color(@color) { 1281 | background:@color; 1282 | .contrast(@color); 1283 | } 1284 | 1285 | .panel-color(@color1c); 1286 | 1287 | &.color1 { .panel-color(@color1);} 1288 | &.color1a { .panel-color(@color1a);} 1289 | &.color1b { .panel-color(@color1b);} 1290 | &.color1c { .panel-color(@color1c);} 1291 | &.color2 { .panel-color(@color2);} 1292 | &.color2a { .panel-color(@color2a);} 1293 | &.color2b { .panel-color(@color2b);} 1294 | &.color2c { .panel-color(@color2c);} 1295 | &.color3 { .panel-color(@color3);} 1296 | &.color3a { .panel-color(@color3a);} 1297 | &.color3b { .panel-color(@color3b);} 1298 | &.color3c { .panel-color(@color3c);} 1299 | &.color4 { .panel-color(@color4);} 1300 | &.color4a { .panel-color(@color4a);} 1301 | &.color4b { .panel-color(@color4b);} 1302 | &.color4c { .panel-color(@color4c);} 1303 | } 1304 | 1305 | .panel-heading, .panel-footer { 1306 | border:none; 1307 | 1308 | } 1309 | 1310 | .panel-body { 1311 | padding-bottom:0; 1312 | } 1313 | 1314 | .panel-footer { 1315 | padding-bottom:15px; 1316 | } 1317 | 1318 | .panel-heading, .panel-body, .panel-footer { 1319 | 1320 | .panel-heading-color(@color) { 1321 | background:@color; 1322 | .contrast(@color); 1323 | } 1324 | 1325 | .panel-heading-color(@color1c); 1326 | 1327 | &.color1 { .panel-heading-color(@color1);} 1328 | &.color1a { .panel-heading-color(@color1a);} 1329 | &.color1b { .panel-heading-color(@color1b);} 1330 | &.color1c { .panel-heading-color(@color1c);} 1331 | &.color2 { .panel-heading-color(@color2);} 1332 | &.color2a { .panel-heading-color(@color2a);} 1333 | &.color2b { .panel-heading-color(@color2b);} 1334 | &.color2c { .panel-heading-color(@color2c);} 1335 | &.color3 { .panel-heading-color(@color3);} 1336 | &.color3a { .panel-heading-color(@color3a);} 1337 | &.color3b { .panel-heading-color(@color3b);} 1338 | &.color3c { .panel-heading-color(@color3c);} 1339 | &.color4 { .panel-heading-color(@color4);} 1340 | &.color4a { .panel-heading-color(@color4a);} 1341 | &.color4b { .panel-heading-color(@color4b);} 1342 | &.color4c { .panel-heading-color(@color4c);} 1343 | } 1344 | 1345 | //carousel 1346 | 1347 | .carousel-control { 1348 | &.left { 1349 | background:transparent; 1350 | } 1351 | 1352 | &.right { 1353 | left: auto; 1354 | right: 0; 1355 | background:transparent; 1356 | } 1357 | 1358 | .icon-nav-defaults() { 1359 | @size:44px; 1360 | font-size:@size; 1361 | line-height:@size; 1362 | text-shadow:none; 1363 | font-weight:bold; 1364 | } 1365 | 1366 | .icon-prev { 1367 | &:before { 1368 | content: '\00ab';// DOUBLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039) 1369 | .icon-nav-defaults(); 1370 | } 1371 | } 1372 | 1373 | .icon-next { 1374 | &:before { 1375 | content: '\00bb';// DOUBLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A) 1376 | .icon-nav-defaults(); 1377 | } 1378 | } 1379 | } 1380 | 1381 | .carousel-indicators { 1382 | @size:24px; 1383 | @border-radius:2px; 1384 | 1385 | li { 1386 | width: (@size - @border-radius); 1387 | height: (@size - @border-radius); 1388 | border-radius: @size; 1389 | margin:@border-radius; 1390 | } 1391 | 1392 | .active { 1393 | width: (@size + @border-radius); 1394 | height: (@size + @border-radius); 1395 | } 1396 | 1397 | .carousel-indicators-color(@color) { 1398 | li { 1399 | border: @border-radius solid @color; 1400 | } 1401 | 1402 | .active { 1403 | background-color:@color; 1404 | } 1405 | } 1406 | 1407 | .carousel-indicators-color(@color1c); 1408 | 1409 | &.color1{ .carousel-indicators-color(@color1c);} 1410 | &.color2{ .carousel-indicators-color(@color2c);} 1411 | &.color3{ .carousel-indicators-color(@color3c);} 1412 | &.color4{ .carousel-indicators-color(@color4c);} 1413 | } 1414 | 1415 | //list groups 1416 | 1417 | .list-group { 1418 | background:transparent; 1419 | } 1420 | 1421 | .list-group-item { 1422 | .list-group-item-color(@color,@active-color) { 1423 | 1424 | background:@color; 1425 | .contrast(@color); 1426 | border-color:darken(@color,4%); 1427 | 1428 | a& { 1429 | .contrast(@color); 1430 | 1431 | &:hover, 1432 | &:focus { 1433 | background-color: lighten(@color,2%); 1434 | } 1435 | 1436 | > .list-group-item-heading { 1437 | .contrast(@color); 1438 | } 1439 | } 1440 | 1441 | &.active, 1442 | &.active:hover, 1443 | &.active:focus { 1444 | .contrast(@active-color); 1445 | background-color: @active-color; 1446 | .btn-borders(@active-color); 1447 | 1448 | .list-group-item-text { 1449 | color: lighten(@active-color, 40%); 1450 | } 1451 | } 1452 | } 1453 | 1454 | .list-group-item-color(@color1b,@color1c); 1455 | 1456 | &.color1 { .list-group-item-color(@color1b,@color1c);} 1457 | &.color2 { .list-group-item-color(@color2b,@color2c);} 1458 | &.color3 { .list-group-item-color(@color3b,@color3c);} 1459 | &.color4 { .list-group-item-color(@color4b,@color4c);} 1460 | } 1461 | 1462 | // sectionals 1463 | .sectional { 1464 | position:relative; 1465 | padding-top:90px; 1466 | padding-bottom:90px; 1467 | 1468 | .sectional-title { 1469 | position:absolute; 1470 | top:12px; 1471 | width:100%; 1472 | text-align:center; 1473 | text-transform:uppercase; 1474 | font-size:14px; 1475 | .opacity(.65); 1476 | } 1477 | 1478 | .sectional-color(@color) { 1479 | background:@color; 1480 | .contrast(@color); 1481 | } 1482 | 1483 | .sectional-color(@1pxdeep-bg); 1484 | 1485 | &.monochrome {.sectional-color(greyscale(@color1));} 1486 | &.highlight {.sectional-color(lighten(@1pxdeep-bg,6%));} 1487 | 1488 | &.color1 {.sectional-color(@color1);} 1489 | &.color1a {.sectional-color(@color1a);} 1490 | &.color1b {.sectional-color(@color1b);} 1491 | &.color1c {.sectional-color(@color1c);} 1492 | &.color2 {.sectional-color(@color2);} 1493 | &.color2a {.sectional-color(@color2a);} 1494 | &.color2b {.sectional-color(@color2b);} 1495 | &.color2c {.sectional-color(@color2c);} 1496 | &.color3 {.sectional-color(@color3);} 1497 | &.color3a {.sectional-color(@color3a);} 1498 | &.color3b {.sectional-color(@color3b);} 1499 | &.color3c {.sectional-color(@color3c);} 1500 | &.color4 {.sectional-color(@color4);} 1501 | &.color4a {.sectional-color(@color4a);} 1502 | &.color4b {.sectional-color(@color4b);} 1503 | &.color4c {.sectional-color(@color4c);} 1504 | } 1505 | 1506 | //well headline fix 1507 | 1508 | .well { 1509 | h1,h2,h3,h4,h5,h6 { 1510 | &:first-child { 1511 | margin-top:0; 1512 | } 1513 | } 1514 | } 1515 | 1516 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/1pxdeep/scheme.less: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////// 2 | ///////////////////////////////////////////////// 3 | //// SchemeLESS v1.0 4 | //// 5 | //// Copyright 2013 Rex Riepe 6 | //// Licensed under the Apache License v2.0 7 | //// http://www.apache.org/licenses/LICENSE-2.0 8 | //// 9 | ///////////////////////////////////////////////// 10 | ///////////////////////////////////////////////// 11 | 12 | /////////////////////////// 13 | ////Customizable values 14 | /////////////////////////// 15 | 16 | // This color is used to generate the scheme 17 | @seed-color:#578562; 18 | 19 | //// Color wheel positions 20 | /////////////////////////// 21 | 22 | // Uncomment the color wheel you want to use 23 | 24 | // Accented analogue (default) 25 | @wheel_pos1:45; @wheel_pos2:315; @wheel_pos3:180; 26 | 27 | // Tetrad 28 | //@wheel_pos1:30; @wheel_pos2:180; @wheel_pos3:210; 29 | 30 | // Triad 31 | //@wheel_pos1:120; @wheel_pos2:240; @wheel_pos3:0; 32 | 33 | // Compliment 34 | //@wheel_pos1:180; @wheel_pos2:0; @wheel_pos3:180; 35 | 36 | // Monochrome 37 | //@wheel_pos1:8; @wheel_pos2:352; @wheel_pos3:0; 38 | 39 | //// Luma breaks 40 | /////////////////////////// 41 | 42 | // Change these for different contrast cutoff points 43 | 44 | @luma-upper-break:80%; 45 | @luma-lower-break:16%; 46 | 47 | //// Relative changes to subcolors (lightness, saturation) 48 | /////////////////////////// 49 | 50 | // Customize these for different relative a, b and c colors 51 | 52 | @contrast:1; 53 | 54 | @color-a-sat:8%*@contrast; 55 | @color-a-lit:-15%*@contrast; 56 | 57 | @color-b-sat:7%*@contrast; 58 | @color-b-lit:-5%*@contrast; 59 | 60 | @color-c-sat:-3%*@contrast; 61 | @color-c-lit:8%*@contrast; 62 | 63 | /////////////////////////// 64 | ////Scheme building 65 | /////////////////////////// 66 | 67 | //// Beginning color values 68 | /////////////////////////// 69 | 70 | @sat:saturation(@seed-color); 71 | @luma:luma(@seed-color); 72 | @lit:lightness(@seed-color); 73 | @tone:desaturate(@seed-color,100%); 74 | 75 | //// Color creation 76 | /////////////////////////// 77 | 78 | //This makes the scheme's colors using the wheel positions 79 | 80 | @l-factor:@luma; // what we'll use for the L in HSL 81 | 82 | @color1:@seed-color; 83 | @color1theme:hsl(hue(spin(@seed-color,0)),@sat,@l-factor); // a color 1 alternate, to keep a, b and c colors consistent 84 | 85 | @color1a:lighten(saturate(@color1theme,@color-a-sat), @color-a-lit); 86 | @color1b:lighten(saturate(@color1theme,@color-b-sat), @color-b-lit); 87 | @color1c:lighten(saturate(@color1theme,@color-c-sat), @color-c-lit); 88 | 89 | @color2:hsl(hue(spin(@seed-color,@wheel_pos1)),@sat,@l-factor); 90 | 91 | @color2a:lighten(saturate(@color2,@color-a-sat), @color-a-lit); 92 | @color2b:lighten(saturate(@color2,@color-b-sat), @color-b-lit); 93 | @color2c:lighten(saturate(@color2,@color-c-sat), @color-c-lit); 94 | 95 | @color3:hsl(hue(spin(@seed-color,@wheel_pos2)),@sat,@l-factor); 96 | 97 | @color3a:lighten(saturate(@color3,@color-a-sat), @color-a-lit); 98 | @color3b:lighten(saturate(@color3,@color-b-sat), @color-b-lit); 99 | @color3c:lighten(saturate(@color3,@color-c-sat), @color-c-lit); 100 | 101 | @color4:hsl(hue(spin(@seed-color,@wheel_pos3)),@sat,@l-factor); 102 | 103 | @color4a:lighten(saturate(@color4,@color-a-sat), @color-a-lit); 104 | @color4b:lighten(saturate(@color4,@color-b-sat), @color-b-lit); 105 | @color4c:lighten(saturate(@color4,@color-c-sat), @color-c-lit); 106 | 107 | /////////////////////////// 108 | //// Mix-ins 109 | /////////////////////////// 110 | 111 | //// Contrast 112 | /////////////////////////// 113 | 114 | // contrasts text against a given background color 115 | 116 | .contrast (@color) when (luma(@color) >= @luma-upper-break) { 117 | //darker text for lighter backgrounds 118 | color:average(darken(@color,30%),#222); 119 | } 120 | 121 | .contrast (@color) when (luma(@color) < @luma-upper-break) { 122 | //white text for everything else 123 | color:#ffffff; 124 | } 125 | 126 | //// Schemify 127 | /////////////////////////// 128 | 129 | // brings outside colors more in line with the current scheme 130 | 131 | .schemify(@color) { //schemifies a background color 132 | @mix-weight:60%; 133 | @new-color:mix(hsl(hue(@color),@sat,@luma),@color,@mix-weight); 134 | background:@new-color; 135 | .contrast(@new-color); 136 | } 137 | 138 | .schemify-text(@color) { 139 | @mix-weight:60%; 140 | @new-color:mix(hsl(hue(@color),@sat,@luma),@color,@mix-weight); 141 | color:@new-color; 142 | .contrast(@color); 143 | } 144 | 145 | --------------------------------------------------------------------------------