Commit 09f663fc665a493e68fa17a7c9dbd0361733aa4d
Exists in
master
and in
1 other branch
Merge branch 'mongoid-3' into pr/289
Conflicts: app/views/apps/show.html.haml
Showing
228 changed files
with
6164 additions
and
2133 deletions
Show diff stats
Too many changes.
To preserve performance only 100 of 228 files displayed.
.gitignore
.rspec
.travis.yml
1 | 1 | language: ruby |
2 | +env: | |
3 | + - COVERAGE=true | |
2 | 4 | rvm: |
5 | + - 2.0.0 | |
3 | 6 | - 1.9.3 |
4 | - - 1.9.2 | |
5 | - - 1.8.7 | |
7 | + - rbx-19mode | |
6 | 8 | services: mongodb |
9 | +#script: ./script/rspec-queue-mongoid.rb --format progress spec | |
10 | +matrix: | |
11 | + allow_failures: | |
12 | + - rvm: rbx-19mode | |
7 | 13 | |
8 | 14 | # To stop Travis from running tests for a new commit, |
9 | 15 | # add the following to your commit message: [ci skip] | ... | ... |
... | ... | @@ -0,0 +1,155 @@ |
1 | +## 0.3.0 - Not released Yet | |
2 | + | |
3 | +### Improvements | |
4 | + | |
5 | +- [#515][] Update to Mongoid 3.1 ([@arthurnn][]) | |
6 | + | |
7 | + | |
8 | +[#515]: https://github.com/errbit/errbit/issues/515 | |
9 | + | |
10 | +## 0.2.1 - Not released Yet | |
11 | + | |
12 | +### Improvements | |
13 | + | |
14 | +- [#552][] Limite size of asset on errbit ([@tscolari][]) | |
15 | + | |
16 | +### Bug Fixes | |
17 | + | |
18 | +- [#558][] Avoid failure if you remote bitbucket_rest_api gem | |
19 | + ([@shingara][]) | |
20 | + | |
21 | +[@shingara]: https://github.com/shingara | |
22 | +[@tscolari]: https://github.com/tscolari | |
23 | + | |
24 | +[#552]: https://github.com/errbit/errbit/issues/552 | |
25 | +[#558]: https://github.com/errbit/errbit/issues/558 | |
26 | + | |
27 | +## 0.2.0 - 2013-09-11 | |
28 | + | |
29 | +### Improvements | |
30 | + | |
31 | +- Update some gems ([@shingara][]) | |
32 | +- [#492][] Improve some Pjax call ([@nfedyashev][]) | |
33 | +- [#428][] Add the support of Unfuddle Issue Tracker ([@parallel588][]) | |
34 | +- Avoid to delete his own user ([@shingara][]) | |
35 | +- [#456][] Avoid to delete admin access of current user logged ([@shingara][]) | |
36 | +- [#253][] Refactor the Fingerprint generation ([@boblail][]) | |
37 | +- [#508][] Merge comments to when you merge problems ([@shingara][]) | |
38 | +- Update the Devise Gem to the last one ([@shingara][]) | |
39 | +- [#524][] Add current user information on the notifer.js ([@roryf][]) | |
40 | +- [#523][] Update javascript-stacktrace ([@aliscott][]) | |
41 | +- [#516][] Add Jira Issue tracker ([@xenji][]) | |
42 | +- [#512][] Add capabilities to configure the use of sendmail to send | |
43 | + email from Errbit ([@shingara][]) | |
44 | +- [#532][] Use https link in Gravatar if you use errbit on https | |
45 | + ([@jeroenj][]) | |
46 | +- [#536][] Order app by name by default ([@2called-chaos][]) | |
47 | +- [#542][] Allow the MONGODB_URL env configuration about Mongodb ([@bacongobbler][]) | |
48 | +- [#530][] Improve the flowdock notification ([@nfedyashev][]) | |
49 | +- [#531][] Improve the HipChat notification message ([@brendonrapp][]) | |
50 | + | |
51 | + | |
52 | +### Bug Fixes | |
53 | + | |
54 | +- [#343][] Fix the ical generation. ([@shingara][]) | |
55 | +- [#503][] Fix issue on where the service_url choose never use. ([@nfedyashev][]) | |
56 | +- [#506][] Fix issue on bitbucket issue tracker creation failed. ([@Gonzih][]) | |
57 | +- [#514][] Add CDATA in xml return by Javascript. ([@mildavw][]) | |
58 | +- [#517][] Javascript escape path from javascript Notifier. ([@roryf][]) | |
59 | +- [#518][] Fix issue when you try launch task errbit:db:update_update_problem_attrs. ([@shingara][]) | |
60 | +- [#526][] Fix issue of pagination after search. ([@shingara][]) | |
61 | +- [#528][] Fix issue of action after search. ([@shingara][]) | |
62 | + | |
63 | +## 0.1.0 - 2013-05-29 | |
64 | + | |
65 | +### Improvements | |
66 | + | |
67 | +- [#474][] Improve message when access denied. ([@chadcf][]) | |
68 | +- [#468][] Launch a repairDatabase in MongoDB database after launching | |
69 | + the clear_resolved task. ([@shingara][]) | |
70 | +- Update gem of Mongoid to 2.7.1 | |
71 | +- Update gem of Mongo to 1.8.5 | |
72 | +- [#457][] Add task information about db:seed in Readme. ([@mildavw][]) | |
73 | +- Add support of Ruby 2.0.0 | |
74 | +- [#475][] Return a HTTP 422 status code when you try push notice with | |
75 | + bad api key. ([@shingara][]) | |
76 | +- Return a 400 http status when you try put a notice without args. | |
77 | + ([@shingara][]) | |
78 | +- [#486][] Add confirms box when you do massive action. ([@manuelmeurer][]) | |
79 | +- [#487][] Add specific template to redmine notification with less useless data. ([@tvdeyen][]) | |
80 | + | |
81 | +### Bug fixes | |
82 | + | |
83 | +- [#469][] Fix issue about the documentation of new heroku addons usage. | |
84 | + ([@adamjt][]) | |
85 | +- [#455][] Avoid raising exception if you comment an exception and no | |
86 | + other user are define to received this comment. ([@alvarobp][]) | |
87 | +- [#453][] Fix ruby 2.0.0 incompatibilities with gem ([@SamSaffron][]) | |
88 | +- [#476][] Fix javascript notifier issue with IE8 ([@sdepold][]) | |
89 | +- [#466][] Fix not see problem if octokit gem not define ([@tamaloa][]) | |
90 | +- [#460][] Fix issue when you try see user with gravatar activate but no | |
91 | + email define to this user ([@ivanyv][]) | |
92 | +- [#478][] Fix issue about calculation of statisque of problem after | |
93 | + merge ([@shingara][]) | |
94 | + | |
95 | +<!-- Issue fix --> | |
96 | + | |
97 | +[#253]: https://github.com/errbit/errbit/issues/253 | |
98 | +[#343]: https://github.com/errbit/errbit/issues/343 | |
99 | +[#428]: https://github.com/errbit/errbit/issues/428 | |
100 | +[#453]: https://github.com/errbit/errbit/issues/453 | |
101 | +[#455]: https://github.com/errbit/errbit/issues/455 | |
102 | +[#456]: https://github.com/errbit/errbit/issues/456 | |
103 | +[#457]: https://github.com/errbit/errbit/issues/457 | |
104 | +[#460]: https://github.com/errbit/errbit/issues/460 | |
105 | +[#466]: https://github.com/errbit/errbit/issues/466 | |
106 | +[#468]: https://github.com/errbit/errbit/issues/468 | |
107 | +[#469]: https://github.com/errbit/errbit/issues/469 | |
108 | +[#474]: https://github.com/errbit/errbit/issues/474 | |
109 | +[#475]: https://github.com/errbit/errbit/issues/475 | |
110 | +[#476]: https://github.com/errbit/errbit/issues/476 | |
111 | +[#478]: https://github.com/errbit/errbit/issues/478 | |
112 | +[#487]: https://github.com/errbit/errbit/issues/487 | |
113 | +[#486]: https://github.com/errbit/errbit/issues/486 | |
114 | +[#492]: https://github.com/errbit/errbit/issues/492 | |
115 | +[#503]: https://github.com/errbit/errbit/issues/503 | |
116 | +[#506]: https://github.com/errbit/errbit/issues/506 | |
117 | +[#508]: https://github.com/errbit/errbit/issues/508 | |
118 | +[#514]: https://github.com/errbit/errbit/issues/514 | |
119 | +[#516]: https://github.com/errbit/errbit/issues/516 | |
120 | +[#517]: https://github.com/errbit/errbit/issues/517 | |
121 | +[#524]: https://github.com/errbit/errbit/issues/524 | |
122 | +[#526]: https://github.com/errbit/errbit/issues/526 | |
123 | +[#528]: https://github.com/errbit/errbit/issues/528 | |
124 | +[#530]: https://github.com/errbit/errbit/issues/530 | |
125 | +[#531]: https://github.com/errbit/errbit/issues/531 | |
126 | +[#532]: https://github.com/errbit/errbit/issues/532 | |
127 | +[#542]: https://github.com/errbit/errbit/issues/542 | |
128 | + | |
129 | +<!-- Contributor on Errbit Thanks to all of them --> | |
130 | + | |
131 | +[@2called-chaos]: https://github.com/2called-chaos | |
132 | +[@Gonzih]: https://github.com/Gonzih | |
133 | +[@SamSaffron]: https://github.com/SamSaffron | |
134 | +[@adamjt]: https://github.com/adamjt | |
135 | +[@aliscott]: http://github.com/aliscott | |
136 | +[@alvarobp]: https://github.com/alvarobp | |
137 | +[@arthurnn]: https://github.com/arthurnn | |
138 | +[@bacongobbler]: https://github.com/bacongobbler | |
139 | +[@boblail]: https://github.com/boblail | |
140 | +[@brendonrapp]: https://github.com/brendonrapp | |
141 | +[@chadcf]: https://github.com/chadcf | |
142 | +[@ivanyv]: https://github.com/ivanyv | |
143 | +[@jeroenj]: https://github.com/jeroenj | |
144 | +[@manuelmeurer]: https://github.com/manuelmeurer | |
145 | +[@mildavw]: https://github.com/mildavw | |
146 | +[@mildavw]: https://github.com/mildavw | |
147 | +[@nfedyashev]: https://github.com/nfedyashev | |
148 | +[@parallel588]: https://github.com/parallel588 | |
149 | +[@roryf]: https://github.com/roryf | |
150 | +[@sdepold]: https://github.com/sdepold | |
151 | +[@shingara]: https://github.com/shingara | |
152 | +[@tamaloa]: https://github.com/tamaloa | |
153 | +[@tvdeyen]: https://github.com/tvdeyen | |
154 | +[@williamn]: https://github.com/williamn | |
155 | +[@xenji]: https://github.com/xenji | ... | ... |
... | ... | @@ -0,0 +1,75 @@ |
1 | +## 0.3.0 - Not released yet | |
2 | + | |
3 | +- [@arthurnn][] | |
4 | +- [@shingara][] | |
5 | + | |
6 | +[@shingara]: https://github.com/shingara | |
7 | +[@arthurnn]: https://github.com/arthurnn | |
8 | + | |
9 | +## 0.2.1 - Not released yet | |
10 | + | |
11 | +- [@shingara][] | |
12 | +- [@tscolari][] | |
13 | + | |
14 | +[@shingara]: https://github.com/shingara | |
15 | +[@tscolari]: https://github.com/tscolari | |
16 | + | |
17 | +## 0.2.0 - 2013-09-11 | |
18 | + | |
19 | +- [@2called-chaos][] | |
20 | +- [@Gonzih][] | |
21 | +- [@aliscott][] | |
22 | +- [@arthurnn][] | |
23 | +- [@bacongobbler][] | |
24 | +- [@boblail][] | |
25 | +- [@brendonrapp][] | |
26 | +- [@jeroenj][] | |
27 | +- [@mildavw][] | |
28 | +- [@nfedyashev][] | |
29 | +- [@parallel588][] | |
30 | +- [@roryf][] | |
31 | +- [@shingara][] | |
32 | +- [@williamn][] | |
33 | +- [@xenji][] | |
34 | + | |
35 | +## 0.1.0 - 2013-05-29 | |
36 | + | |
37 | +- [@manuelmeurer][] | |
38 | +- [@mildavw][] | |
39 | +- [@chadcf][] | |
40 | +- [@shingara][] | |
41 | +- [@tvdeyen][] | |
42 | +- [@adamjt][] | |
43 | +- [@alvarobp][] | |
44 | +- [@SamSaffron][] | |
45 | +- [@sdepold][] | |
46 | +- [@tamaloa][] | |
47 | +- [@ivanyv][] | |
48 | + | |
49 | +<!-- Contributor on Errbit Thanks to all of them --> | |
50 | + | |
51 | +[@2called-chaos]: https://github.com/2called-chaos | |
52 | +[@Gonzih]: https://github.com/Gonzih | |
53 | +[@SamSaffron]: https://github.com/SamSaffron | |
54 | +[@adamjt]: https://github.com/adamjt | |
55 | +[@aliscott]: http://github.com/aliscott | |
56 | +[@alvarobp]: https://github.com/alvarobp | |
57 | +[@arthurnn]: https://github.com/arthurnn | |
58 | +[@bacongobbler]: https://github.com/bacongobbler | |
59 | +[@boblail]: https://github.com/boblail | |
60 | +[@brendonrapp]: https://github.com/brendonrapp | |
61 | +[@chadcf]: https://github.com/chadcf | |
62 | +[@ivanyv]: https://github.com/ivanyv | |
63 | +[@jeroenj]: https://github.com/jeroenj | |
64 | +[@manuelmeurer]: https://github.com/manuelmeurer | |
65 | +[@mildavw]: https://github.com/mildavw | |
66 | +[@mildavw]: https://github.com/mildavw | |
67 | +[@nfedyashev]: https://github.com/nfedyashev | |
68 | +[@parallel588]: https://github.com/parallel588 | |
69 | +[@roryf]: https://github.com/roryf | |
70 | +[@sdepold]: https://github.com/sdepold | |
71 | +[@shingara]: https://github.com/shingara | |
72 | +[@tamaloa]: https://github.com/tamaloa | |
73 | +[@tvdeyen]: https://github.com/tvdeyen | |
74 | +[@williamn]: https://github.com/williamn | |
75 | +[@xenji]: https://github.com/xenji | ... | ... |
Gemfile
1 | 1 | source 'http://rubygems.org' |
2 | 2 | |
3 | -gem 'rails', '3.2.8' | |
4 | -gem 'mongoid', '~> 2.4.10' | |
5 | -gem 'mongoid_rails_migrations' | |
6 | -gem 'devise', '~> 1.5.3' | |
3 | +gem 'rails', '~> 3.2.13' | |
4 | +gem 'mongoid', '~> 3.1.4' | |
5 | + | |
6 | +gem 'mongoid_rails_migrations', '~> 1.0.1' | |
7 | +gem 'devise' | |
7 | 8 | gem 'haml' |
8 | -gem 'htmlentities', "~> 4.3.0" | |
9 | +gem 'htmlentities' | |
9 | 10 | gem 'rack-ssl', :require => 'rack/ssl' # force SSL |
10 | 11 | |
11 | -gem 'useragent', '~> 0.3.1' | |
12 | -gem 'inherited_resources' | |
12 | +gem 'useragent' | |
13 | +gem 'decent_exposure' | |
14 | +gem 'strong_parameters' | |
13 | 15 | gem 'SystemTimer', :platform => :ruby_18 |
14 | -gem 'actionmailer_inline_css', "~> 1.3.0" | |
15 | -gem 'kaminari' | |
16 | -gem 'rack-ssl-enforcer' | |
16 | +gem 'actionmailer_inline_css' | |
17 | +gem 'kaminari', '>= 0.14.1' | |
18 | +gem 'rack-ssl-enforcer', :require => false | |
19 | +# fabrication 1.3.0 is last supporting ruby 1.8. Update when stop supporting this version too | |
17 | 20 | gem 'fabrication', "~> 1.3.0" # Used for both tests and demo data |
18 | -gem 'rails_autolink', '~> 1.0.9' | |
21 | +gem 'rails_autolink' | |
19 | 22 | # Please don't update hoptoad_notifier to airbrake. |
20 | 23 | # It's for internal use only, and we monkeypatch certain methods |
21 | 24 | gem 'hoptoad_notifier', "~> 2.4" |
... | ... | @@ -35,68 +38,87 @@ gem 'pivotal-tracker' |
35 | 38 | # Fogbugz |
36 | 39 | gem 'ruby-fogbugz', :require => 'fogbugz' |
37 | 40 | # Github Issues |
38 | -gem 'octokit', '~> 1.0.0' | |
41 | +gem 'octokit' | |
39 | 42 | # Gitlab |
40 | -gem 'gitlab' | |
43 | +gem 'gitlab', :git => 'https://github.com/NARKOZ/gitlab.git' | |
41 | 44 | |
42 | 45 | # Bitbucket Issues |
43 | -gem 'bitbucket_rest_api' | |
46 | +gem 'bitbucket_rest_api', :require => false | |
47 | + | |
48 | +# Unfuddle | |
49 | +gem "taskmapper", "~> 0.8.0" | |
50 | +gem "taskmapper-unfuddle", "~> 0.7.0" | |
51 | + | |
52 | +# Jira | |
53 | +gem 'jira-ruby', :require => 'jira' | |
44 | 54 | |
45 | 55 | # Notification services |
46 | 56 | # --------------------------------------- |
47 | -# Campfire | |
48 | -gem 'campy' | |
57 | +# Campfire ( We can't upgrade to 1.0 because drop support of ruby 1.8 | |
58 | +gem 'campy', '0.1.3' | |
49 | 59 | # Hipchat |
50 | 60 | gem 'hipchat' |
51 | 61 | # Google Talk |
52 | -gem 'xmpp4r' | |
62 | +gem 'xmpp4r', :require => ["xmpp4r", "xmpp4r/muc"] | |
53 | 63 | # Hoiio (SMS) |
54 | 64 | gem 'hoi' |
55 | 65 | # Pushover (iOS Push notifications) |
56 | 66 | gem 'rushover' |
67 | +# Hubot | |
68 | +gem 'httparty' | |
69 | +# Flowdock | |
70 | +gem 'flowdock' | |
57 | 71 | |
58 | 72 | # Authentication |
59 | 73 | # --------------------------------------- |
60 | 74 | # GitHub OAuth |
61 | 75 | gem 'omniauth-github' |
62 | 76 | |
63 | - | |
64 | -platform :ruby do | |
65 | - gem 'mongo', '= 1.6.2' | |
66 | - gem 'bson', '= 1.6.2' | |
67 | - gem 'bson_ext', '= 1.6.2' | |
68 | -end | |
69 | - | |
70 | 77 | gem 'ri_cal' |
71 | 78 | gem 'yajl-ruby', :require => "yajl" |
72 | 79 | |
73 | 80 | group :development, :test do |
74 | 81 | gem 'rspec-rails', '~> 2.6' |
75 | 82 | gem 'webmock', :require => false |
76 | - unless ENV["CI"] | |
77 | - gem 'ruby-debug', :platform => :mri_18 | |
78 | - gem 'debugger', :platform => :mri_19 | |
79 | - gem 'pry-rails' | |
80 | - end | |
83 | + gem 'airbrake', :require => false | |
84 | + gem 'ruby-debug', :platform => :mri_18 | |
85 | + gem 'debugger', :platform => :mri_19 | |
86 | + gem 'pry-rails' | |
81 | 87 | # gem 'rpm_contrib' |
82 | 88 | # gem 'newrelic_rpm' |
89 | + gem 'quiet_assets' | |
90 | +end | |
91 | + | |
92 | +group :development do | |
83 | 93 | gem 'capistrano' |
94 | + | |
95 | + # better errors | |
96 | + gem 'better_errors' , :platform => :ruby_19 | |
97 | + gem 'binding_of_caller', :platform => :ruby_19 | |
98 | + gem 'meta_request' , :platform => :ruby_19 | |
99 | + gem 'foreman' | |
100 | + | |
101 | + # Use thin for development | |
102 | + gem 'thin', :group => :development, :platform => :ruby | |
103 | + | |
84 | 104 | end |
85 | 105 | |
86 | 106 | group :test do |
87 | - gem 'capybara' | |
107 | + # Capybara 2.1.0 no more support 1.8.7 ruby version | |
108 | + gem 'capybara', "~> 2.0.1" | |
88 | 109 | gem 'launchy' |
89 | - gem 'database_cleaner', '~> 0.6.0' | |
110 | + # DatabaseCleaner 1.0.0 drop the support of ruby 1.8.7 | |
111 | + gem 'database_cleaner', '~> 0.9.0' | |
90 | 112 | gem 'email_spec' |
91 | - gem 'timecop' | |
113 | + gem 'timecop', '0.6.1' # last version compatible to ruby 1.8 | |
114 | + gem 'coveralls', :require => false | |
115 | + gem 'mongoid-rspec', :require => false | |
92 | 116 | end |
93 | 117 | |
94 | 118 | group :heroku, :production do |
95 | 119 | gem 'unicorn' |
96 | 120 | end |
97 | 121 | |
98 | -# Use thin for development | |
99 | -gem 'thin', :group => :development, :platform => :ruby | |
100 | 122 | |
101 | 123 | # Gems used only for assets and not required |
102 | 124 | # in production environments by default. |
... | ... | @@ -104,7 +126,10 @@ group :assets do |
104 | 126 | gem 'execjs' |
105 | 127 | gem 'therubyracer', :platform => :ruby # C Ruby (MRI) or Rubinius, but NOT Windows |
106 | 128 | gem 'uglifier', '>= 1.0.3' |
129 | + # We can't upgrade because not compatible to jquery >= 1.9. | |
130 | + # To do that, we need fix the rails.js | |
131 | + gem 'jquery-rails', '~> 2.1.4' | |
132 | + gem 'pjax_rails' | |
107 | 133 | gem 'underscore-rails' |
134 | + gem 'turbo-sprockets-rails3' | |
108 | 135 | end |
109 | - | |
110 | -gem 'turbo-sprockets-rails3' | ... | ... |
Gemfile.lock
1 | +GIT | |
2 | + remote: https://github.com/NARKOZ/gitlab.git | |
3 | + revision: 7a00d38c53335010d2fb8a233247fe2c97338903 | |
4 | + specs: | |
5 | + gitlab (2.2.0) | |
6 | + httparty | |
7 | + | |
1 | 8 | GEM |
2 | 9 | remote: http://rubygems.org/ |
3 | 10 | specs: |
4 | 11 | SystemTimer (1.2.3) |
5 | - actionmailer (3.2.8) | |
6 | - actionpack (= 3.2.8) | |
7 | - mail (~> 2.4.4) | |
8 | - actionmailer_inline_css (1.3.1) | |
12 | + actionmailer (3.2.13) | |
13 | + actionpack (= 3.2.13) | |
14 | + mail (~> 2.5.3) | |
15 | + actionmailer_inline_css (1.5.3) | |
9 | 16 | actionmailer (>= 3.0.0) |
10 | 17 | nokogiri (>= 1.4.4) |
11 | 18 | premailer (>= 1.7.1) |
12 | - actionpack (3.2.8) | |
13 | - activemodel (= 3.2.8) | |
14 | - activesupport (= 3.2.8) | |
19 | + actionpack (3.2.13) | |
20 | + activemodel (= 3.2.13) | |
21 | + activesupport (= 3.2.13) | |
15 | 22 | builder (~> 3.0.0) |
16 | 23 | erubis (~> 2.7.0) |
17 | 24 | journey (~> 1.0.4) |
18 | - rack (~> 1.4.0) | |
25 | + rack (~> 1.4.5) | |
19 | 26 | rack-cache (~> 1.2) |
20 | 27 | rack-test (~> 0.6.1) |
21 | - sprockets (~> 2.1.3) | |
22 | - activemodel (3.2.8) | |
23 | - activesupport (= 3.2.8) | |
28 | + sprockets (~> 2.2.1) | |
29 | + activemodel (3.2.13) | |
30 | + activesupport (= 3.2.13) | |
24 | 31 | builder (~> 3.0.0) |
25 | - activerecord (3.2.8) | |
26 | - activemodel (= 3.2.8) | |
27 | - activesupport (= 3.2.8) | |
32 | + activerecord (3.2.13) | |
33 | + activemodel (= 3.2.13) | |
34 | + activesupport (= 3.2.13) | |
28 | 35 | arel (~> 3.0.2) |
29 | 36 | tzinfo (~> 0.3.29) |
30 | - activeresource (3.2.8) | |
31 | - activemodel (= 3.2.8) | |
32 | - activesupport (= 3.2.8) | |
33 | - activesupport (3.2.8) | |
34 | - i18n (~> 0.6) | |
37 | + activeresource (3.2.13) | |
38 | + activemodel (= 3.2.13) | |
39 | + activesupport (= 3.2.13) | |
40 | + activesupport (3.2.13) | |
41 | + i18n (= 0.6.1) | |
35 | 42 | multi_json (~> 1.0) |
36 | - addressable (2.3.2) | |
43 | + addressable (2.3.5) | |
44 | + airbrake (3.1.13) | |
45 | + builder | |
46 | + json | |
37 | 47 | arel (3.0.2) |
38 | - bcrypt-ruby (3.0.1) | |
39 | - bitbucket_rest_api (0.1.1) | |
48 | + bcrypt-ruby (3.1.1) | |
49 | + better_errors (0.9.0) | |
50 | + coderay (>= 1.0.0) | |
51 | + erubis (>= 2.6.6) | |
52 | + binding_of_caller (0.7.2) | |
53 | + debug_inspector (>= 0.0.1) | |
54 | + bitbucket_rest_api (0.1.2) | |
40 | 55 | faraday (~> 0.8.1) |
41 | 56 | faraday_middleware (~> 0.8.1) |
42 | 57 | hashie (~> 1.2.0) |
43 | 58 | multi_json (~> 1.3) |
44 | 59 | nokogiri (~> 1.5.2) |
45 | 60 | simple_oauth |
46 | - bson (1.6.2) | |
47 | - bson_ext (1.6.2) | |
48 | - bson (~> 1.6.2) | |
49 | 61 | builder (3.0.4) |
62 | + callsite (0.0.11) | |
50 | 63 | campy (0.1.3) |
51 | 64 | multi_json (~> 1.0) |
52 | - capistrano (2.13.5) | |
65 | + capistrano (2.15.5) | |
53 | 66 | highline |
54 | 67 | net-scp (>= 1.0.0) |
55 | 68 | net-sftp (>= 2.0.0) |
56 | 69 | net-ssh (>= 2.0.14) |
57 | 70 | net-ssh-gateway (>= 1.1.0) |
58 | - capybara (1.1.2) | |
71 | + capybara (2.0.3) | |
59 | 72 | mime-types (>= 1.16) |
60 | 73 | nokogiri (>= 1.3.3) |
61 | 74 | rack (>= 1.0.0) |
62 | 75 | rack-test (>= 0.5.4) |
63 | 76 | selenium-webdriver (~> 2.0) |
64 | - xpath (~> 0.1.4) | |
65 | - childprocess (0.3.5) | |
66 | - ffi (~> 1.0, >= 1.0.6) | |
67 | - coderay (1.0.6) | |
77 | + xpath (~> 1.0.0) | |
78 | + childprocess (0.3.9) | |
79 | + ffi (~> 1.0, >= 1.0.11) | |
80 | + coderay (1.0.9) | |
81 | + colorize (0.5.8) | |
68 | 82 | columnize (0.3.6) |
69 | - crack (0.3.1) | |
70 | - css_parser (1.2.6) | |
83 | + coveralls (0.6.7) | |
84 | + colorize | |
85 | + multi_json (~> 1.3) | |
86 | + rest-client | |
87 | + simplecov (>= 0.7) | |
88 | + thor | |
89 | + crack (0.4.1) | |
90 | + safe_yaml (~> 0.9.0) | |
91 | + css_parser (1.3.4) | |
71 | 92 | addressable |
72 | - rdoc | |
73 | - daemons (1.1.8) | |
74 | - database_cleaner (0.6.7) | |
75 | - debugger (1.2.1) | |
93 | + daemons (1.1.9) | |
94 | + database_cleaner (0.9.1) | |
95 | + debug_inspector (0.0.2) | |
96 | + debugger (1.6.1) | |
76 | 97 | columnize (>= 0.3.1) |
77 | - debugger-linecache (~> 1.1.1) | |
78 | - debugger-ruby_core_source (~> 1.1.4) | |
79 | - debugger-linecache (1.1.2) | |
80 | - debugger-ruby_core_source (>= 1.1.1) | |
81 | - debugger-ruby_core_source (1.1.4) | |
82 | - devise (1.5.3) | |
98 | + debugger-linecache (~> 1.2.0) | |
99 | + debugger-ruby_core_source (~> 1.2.3) | |
100 | + debugger-linecache (1.2.0) | |
101 | + debugger-ruby_core_source (1.2.3) | |
102 | + decent_exposure (2.2.1) | |
103 | + devise (2.2.7) | |
83 | 104 | bcrypt-ruby (~> 3.0) |
84 | - orm_adapter (~> 0.0.3) | |
85 | - warden (~> 1.1) | |
86 | - diff-lcs (1.1.3) | |
87 | - email_spec (1.2.1) | |
105 | + orm_adapter (~> 0.1) | |
106 | + railties (~> 3.1) | |
107 | + warden (~> 1.2.1) | |
108 | + diff-lcs (1.2.4) | |
109 | + dotenv (0.8.0) | |
110 | + email_spec (1.5.0) | |
111 | + launchy (~> 2.1) | |
88 | 112 | mail (~> 2.2) |
89 | - rspec (~> 2.0) | |
90 | 113 | erubis (2.7.0) |
91 | - eventmachine (0.12.10) | |
114 | + eventmachine (1.0.3) | |
92 | 115 | execjs (1.4.0) |
93 | 116 | multi_json (~> 1.0) |
94 | 117 | fabrication (1.3.2) |
95 | - faraday (0.8.4) | |
96 | - multipart-post (~> 1.1) | |
118 | + faraday (0.8.8) | |
119 | + multipart-post (~> 1.2.0) | |
97 | 120 | faraday_middleware (0.8.8) |
98 | 121 | faraday (>= 0.7.4, < 0.9) |
99 | - ffi (1.1.4) | |
100 | - haml (3.1.6) | |
122 | + ffi (1.9.0) | |
123 | + flowdock (0.3.1) | |
124 | + httparty (~> 0.7) | |
125 | + multi_json | |
126 | + foreman (0.63.0) | |
127 | + dotenv (>= 0.7) | |
128 | + thor (>= 0.13.6) | |
129 | + haml (4.0.3) | |
130 | + tilt | |
101 | 131 | happymapper (0.4.0) |
102 | 132 | libxml-ruby (~> 2.0) |
103 | - has_scope (0.5.1) | |
104 | 133 | hashie (1.2.0) |
105 | - highline (1.6.15) | |
106 | - hike (1.2.1) | |
107 | - hipchat (0.4.1) | |
134 | + highline (1.6.19) | |
135 | + hike (1.2.3) | |
136 | + hipchat (0.11.0) | |
108 | 137 | httparty |
109 | 138 | hoi (0.0.6) |
110 | 139 | httparty (> 0.6.0) |
... | ... | @@ -113,152 +142,171 @@ GEM |
113 | 142 | activesupport |
114 | 143 | builder |
115 | 144 | htmlentities (4.3.1) |
116 | - httparty (0.9.0) | |
145 | + httparty (0.11.0) | |
117 | 146 | multi_json (~> 1.0) |
118 | - multi_xml | |
119 | - httpauth (0.1) | |
147 | + multi_xml (>= 0.5.2) | |
148 | + httpauth (0.2.0) | |
120 | 149 | i18n (0.6.1) |
121 | - inherited_resources (1.3.1) | |
122 | - has_scope (~> 0.5.0) | |
123 | - responders (~> 0.6) | |
150 | + jira-ruby (0.1.2) | |
151 | + activesupport | |
152 | + oauth | |
153 | + railties | |
124 | 154 | journey (1.0.4) |
125 | - json (1.7.5) | |
126 | - jwt (0.1.5) | |
127 | - multi_json (>= 1.0) | |
155 | + jquery-rails (2.1.4) | |
156 | + railties (>= 3.0, < 5.0) | |
157 | + thor (>= 0.14, < 2.0) | |
158 | + json (1.8.0) | |
159 | + jwt (0.1.8) | |
160 | + multi_json (>= 1.5) | |
128 | 161 | kaminari (0.14.1) |
129 | 162 | actionpack (>= 3.0.0) |
130 | 163 | activesupport (>= 3.0.0) |
131 | - kgio (2.7.4) | |
132 | - launchy (2.1.2) | |
164 | + kgio (2.8.0) | |
165 | + launchy (2.3.0) | |
133 | 166 | addressable (~> 2.3) |
134 | - libv8 (3.3.10.4) | |
135 | - libwebsocket (0.1.5) | |
136 | - addressable | |
137 | - libxml-ruby (2.3.3) | |
167 | + libv8 (3.16.14.3) | |
168 | + libxml-ruby (2.7.0) | |
138 | 169 | lighthouse-api (2.0) |
139 | 170 | activeresource (>= 3.0.0) |
140 | 171 | activesupport (>= 3.0.0) |
141 | 172 | linecache (0.46) |
142 | 173 | rbx-require-relative (> 0.0.4) |
143 | - mail (2.4.4) | |
144 | - i18n (>= 0.4.0) | |
174 | + mail (2.5.4) | |
145 | 175 | mime-types (~> 1.16) |
146 | 176 | treetop (~> 1.4.8) |
147 | - method_source (0.7.1) | |
148 | - mime-types (1.19) | |
149 | - mongo (1.6.2) | |
150 | - bson (~> 1.6.2) | |
151 | - mongoid (2.4.10) | |
152 | - activemodel (~> 3.1) | |
153 | - mongo (~> 1.3) | |
177 | + meta_request (0.2.8) | |
178 | + callsite | |
179 | + rack-contrib | |
180 | + railties | |
181 | + method_source (0.8.2) | |
182 | + mime-types (1.24) | |
183 | + mongoid (3.1.4) | |
184 | + activemodel (~> 3.2) | |
185 | + moped (~> 1.4) | |
186 | + origin (~> 1.0) | |
154 | 187 | tzinfo (~> 0.3.22) |
155 | - mongoid_rails_migrations (0.0.14) | |
156 | - activesupport (>= 3.0.0) | |
188 | + mongoid-rspec (1.9.0) | |
189 | + mongoid (>= 3.0.1) | |
190 | + rake | |
191 | + rspec (>= 2.14) | |
192 | + mongoid_rails_migrations (1.0.1) | |
193 | + activesupport (>= 3.2.0) | |
157 | 194 | bundler (>= 1.0.0) |
158 | - rails (>= 3.0.0) | |
159 | - railties (>= 3.0.0) | |
160 | - multi_json (1.3.6) | |
161 | - multi_xml (0.5.1) | |
162 | - multipart-post (1.1.5) | |
163 | - net-scp (1.0.4) | |
164 | - net-ssh (>= 1.99.1) | |
165 | - net-sftp (2.0.5) | |
166 | - net-ssh (>= 2.0.9) | |
167 | - net-ssh (2.6.1) | |
168 | - net-ssh-gateway (1.1.0) | |
169 | - net-ssh (>= 1.99.1) | |
170 | - nokogiri (1.5.5) | |
171 | - oauth2 (0.8.0) | |
195 | + rails (>= 3.2.0) | |
196 | + railties (>= 3.2.0) | |
197 | + moped (1.5.1) | |
198 | + multi_json (1.7.9) | |
199 | + multi_xml (0.5.5) | |
200 | + multipart-post (1.2.0) | |
201 | + net-scp (1.1.2) | |
202 | + net-ssh (>= 2.6.5) | |
203 | + net-sftp (2.1.2) | |
204 | + net-ssh (>= 2.6.5) | |
205 | + net-ssh (2.6.8) | |
206 | + net-ssh-gateway (1.2.0) | |
207 | + net-ssh (>= 2.6.5) | |
208 | + nokogiri (1.5.10) | |
209 | + nokogiri-happymapper (0.5.7) | |
210 | + nokogiri (~> 1.5) | |
211 | + oauth (0.4.7) | |
212 | + oauth2 (0.8.1) | |
172 | 213 | faraday (~> 0.8) |
173 | 214 | httpauth (~> 0.1) |
174 | 215 | jwt (~> 0.1.4) |
175 | 216 | multi_json (~> 1.0) |
176 | 217 | rack (~> 1.2) |
177 | - octokit (1.0.7) | |
218 | + octokit (1.18.0) | |
178 | 219 | addressable (~> 2.2) |
179 | 220 | faraday (~> 0.8) |
180 | 221 | faraday_middleware (~> 0.8) |
181 | 222 | hashie (~> 1.2) |
182 | 223 | multi_json (~> 1.3) |
183 | - omniauth (1.1.1) | |
184 | - hashie (~> 1.2) | |
224 | + omniauth (1.1.4) | |
225 | + hashie (>= 1.2, < 3) | |
185 | 226 | rack |
186 | - omniauth-github (1.0.2) | |
227 | + omniauth-github (1.1.1) | |
187 | 228 | omniauth (~> 1.0) |
188 | 229 | omniauth-oauth2 (~> 1.1) |
189 | 230 | omniauth-oauth2 (1.1.1) |
190 | 231 | oauth2 (~> 0.8.0) |
191 | 232 | omniauth (~> 1.0) |
192 | - orm_adapter (0.0.7) | |
233 | + origin (1.1.0) | |
234 | + orm_adapter (0.4.0) | |
193 | 235 | oruen_redmine_client (0.0.1) |
194 | 236 | activeresource (>= 2.3.0) |
195 | - pivotal-tracker (0.5.4) | |
196 | - builder | |
237 | + pivotal-tracker (0.5.10) | |
197 | 238 | builder |
198 | - happymapper (>= 0.3.2) | |
239 | + crack | |
199 | 240 | happymapper (>= 0.3.2) |
200 | 241 | nokogiri (>= 1.4.3) |
201 | - nokogiri (~> 1.4) | |
202 | - rest-client (~> 1.6.0) | |
242 | + nokogiri (>= 1.5.5) | |
243 | + nokogiri-happymapper (>= 0.5.4) | |
203 | 244 | rest-client (~> 1.6.0) |
245 | + pjax_rails (0.3.4) | |
246 | + jquery-rails | |
204 | 247 | polyglot (0.3.3) |
205 | 248 | premailer (1.7.3) |
206 | 249 | css_parser (>= 1.1.9) |
207 | 250 | htmlentities (>= 4.0.0) |
208 | - pry (0.9.9.6) | |
251 | + pry (0.9.12.2) | |
209 | 252 | coderay (~> 1.0.5) |
210 | - method_source (~> 0.7.1) | |
211 | - slop (>= 2.4.4, < 3) | |
212 | - pry-rails (0.2.0) | |
213 | - pry | |
214 | - rack (1.4.1) | |
253 | + method_source (~> 0.8) | |
254 | + slop (~> 3.4) | |
255 | + pry-rails (0.3.2) | |
256 | + pry (>= 0.9.10) | |
257 | + quiet_assets (1.0.2) | |
258 | + railties (>= 3.1, < 5.0) | |
259 | + rack (1.4.5) | |
215 | 260 | rack-cache (1.2) |
216 | 261 | rack (>= 0.4) |
217 | - rack-ssl (1.3.2) | |
262 | + rack-contrib (1.1.0) | |
263 | + rack (>= 0.9.1) | |
264 | + rack-ssl (1.3.3) | |
218 | 265 | rack |
219 | - rack-ssl-enforcer (0.2.4) | |
266 | + rack-ssl-enforcer (0.2.5) | |
220 | 267 | rack-test (0.6.2) |
221 | 268 | rack (>= 1.0) |
222 | - rails (3.2.8) | |
223 | - actionmailer (= 3.2.8) | |
224 | - actionpack (= 3.2.8) | |
225 | - activerecord (= 3.2.8) | |
226 | - activeresource (= 3.2.8) | |
227 | - activesupport (= 3.2.8) | |
269 | + rails (3.2.13) | |
270 | + actionmailer (= 3.2.13) | |
271 | + actionpack (= 3.2.13) | |
272 | + activerecord (= 3.2.13) | |
273 | + activeresource (= 3.2.13) | |
274 | + activesupport (= 3.2.13) | |
228 | 275 | bundler (~> 1.0) |
229 | - railties (= 3.2.8) | |
230 | - rails_autolink (1.0.9) | |
231 | - rails (~> 3.1) | |
232 | - railties (3.2.8) | |
233 | - actionpack (= 3.2.8) | |
234 | - activesupport (= 3.2.8) | |
276 | + railties (= 3.2.13) | |
277 | + rails_autolink (1.1.0) | |
278 | + rails (> 3.1) | |
279 | + railties (3.2.13) | |
280 | + actionpack (= 3.2.13) | |
281 | + activesupport (= 3.2.13) | |
235 | 282 | rack-ssl (~> 1.3.2) |
236 | 283 | rake (>= 0.8.7) |
237 | 284 | rdoc (~> 3.4) |
238 | 285 | thor (>= 0.14.6, < 2.0) |
239 | - raindrops (0.10.0) | |
240 | - rake (0.9.2.2) | |
286 | + raindrops (0.11.0) | |
287 | + rake (10.1.0) | |
241 | 288 | rbx-require-relative (0.0.9) |
242 | - rdoc (3.12) | |
289 | + rdoc (3.12.2) | |
243 | 290 | json (~> 1.4) |
244 | - responders (0.9.2) | |
245 | - railties (~> 3.1) | |
291 | + ref (1.0.5) | |
246 | 292 | rest-client (1.6.7) |
247 | 293 | mime-types (>= 1.16) |
248 | 294 | ri_cal (0.8.8) |
249 | - rspec (2.11.0) | |
250 | - rspec-core (~> 2.11.0) | |
251 | - rspec-expectations (~> 2.11.0) | |
252 | - rspec-mocks (~> 2.11.0) | |
253 | - rspec-core (2.11.1) | |
254 | - rspec-expectations (2.11.2) | |
255 | - diff-lcs (~> 1.1.3) | |
256 | - rspec-mocks (2.11.1) | |
257 | - rspec-rails (2.11.0) | |
295 | + rspec (2.14.1) | |
296 | + rspec-core (~> 2.14.0) | |
297 | + rspec-expectations (~> 2.14.0) | |
298 | + rspec-mocks (~> 2.14.0) | |
299 | + rspec-core (2.14.5) | |
300 | + rspec-expectations (2.14.2) | |
301 | + diff-lcs (>= 1.1.3, < 2.0) | |
302 | + rspec-mocks (2.14.3) | |
303 | + rspec-rails (2.14.0) | |
258 | 304 | actionpack (>= 3.0) |
259 | 305 | activesupport (>= 3.0) |
260 | 306 | railties (>= 3.0) |
261 | - rspec (~> 2.11.0) | |
307 | + rspec-core (~> 2.14.0) | |
308 | + rspec-expectations (~> 2.14.0) | |
309 | + rspec-mocks (~> 2.14.0) | |
262 | 310 | ruby-debug (0.10.4) |
263 | 311 | columnize (>= 0.1) |
264 | 312 | ruby-debug-base (~> 0.10.4.0) |
... | ... | @@ -267,52 +315,71 @@ GEM |
267 | 315 | ruby-fogbugz (0.1.1) |
268 | 316 | crack |
269 | 317 | rubyzip (0.9.9) |
270 | - rushover (0.1.1) | |
318 | + rushover (0.3.0) | |
271 | 319 | json |
272 | 320 | rest-client |
273 | - selenium-webdriver (2.25.0) | |
321 | + safe_yaml (0.9.5) | |
322 | + selenium-webdriver (2.35.0) | |
274 | 323 | childprocess (>= 0.2.5) |
275 | - libwebsocket (~> 0.1.3) | |
276 | 324 | multi_json (~> 1.0) |
277 | 325 | rubyzip |
278 | - simple_oauth (0.1.9) | |
279 | - slop (2.4.4) | |
280 | - sprockets (2.1.3) | |
326 | + websocket (~> 1.0.4) | |
327 | + simple_oauth (0.2.0) | |
328 | + simplecov (0.7.1) | |
329 | + multi_json (~> 1.0) | |
330 | + simplecov-html (~> 0.7.1) | |
331 | + simplecov-html (0.7.1) | |
332 | + slop (3.4.6) | |
333 | + sprockets (2.2.2) | |
281 | 334 | hike (~> 1.2) |
335 | + multi_json (~> 1.0) | |
282 | 336 | rack (~> 1.0) |
283 | 337 | tilt (~> 1.1, != 1.3.0) |
284 | - therubyracer (0.10.2) | |
285 | - libv8 (~> 3.3.10) | |
286 | - thin (1.4.1) | |
338 | + strong_parameters (0.2.1) | |
339 | + actionpack (~> 3.0) | |
340 | + activemodel (~> 3.0) | |
341 | + railties (~> 3.0) | |
342 | + taskmapper (0.8.0) | |
343 | + activeresource (~> 3.0) | |
344 | + activesupport (~> 3.0) | |
345 | + hashie (~> 1.2) | |
346 | + taskmapper-unfuddle (0.7.0) | |
347 | + addressable (~> 2.2) | |
348 | + taskmapper (~> 0.8) | |
349 | + therubyracer (0.12.0) | |
350 | + libv8 (~> 3.16.14.0) | |
351 | + ref | |
352 | + thin (1.5.1) | |
287 | 353 | daemons (>= 1.0.9) |
288 | 354 | eventmachine (>= 0.12.6) |
289 | 355 | rack (>= 1.0.0) |
290 | - thor (0.16.0) | |
291 | - tilt (1.3.3) | |
292 | - timecop (0.3.5) | |
293 | - treetop (1.4.10) | |
356 | + thor (0.18.1) | |
357 | + tilt (1.4.1) | |
358 | + timecop (0.6.1) | |
359 | + treetop (1.4.15) | |
294 | 360 | polyglot |
295 | 361 | polyglot (>= 0.3.1) |
296 | - turbo-sprockets-rails3 (0.2.12) | |
297 | - railties (>= 3.1.0, < 3.2.9) | |
362 | + turbo-sprockets-rails3 (0.3.9) | |
363 | + railties (> 3.2.8, < 4.0.0) | |
298 | 364 | sprockets (>= 2.0.0) |
299 | - tzinfo (0.3.33) | |
300 | - uglifier (1.2.7) | |
365 | + tzinfo (0.3.37) | |
366 | + uglifier (2.1.2) | |
301 | 367 | execjs (>= 0.3.0) |
302 | - multi_json (~> 1.3) | |
303 | - underscore-rails (1.4.2.1) | |
304 | - unicorn (4.3.1) | |
368 | + multi_json (~> 1.0, >= 1.0.2) | |
369 | + underscore-rails (1.5.1) | |
370 | + unicorn (4.6.3) | |
305 | 371 | kgio (~> 2.6) |
306 | 372 | rack |
307 | 373 | raindrops (~> 0.7) |
308 | - useragent (0.3.2) | |
309 | - warden (1.2.1) | |
374 | + useragent (0.6.0) | |
375 | + warden (1.2.3) | |
310 | 376 | rack (>= 1.0) |
311 | - webmock (1.8.7) | |
377 | + webmock (1.13.0) | |
312 | 378 | addressable (>= 2.2.7) |
313 | - crack (>= 0.1.7) | |
379 | + crack (>= 0.3.2) | |
380 | + websocket (1.0.7) | |
314 | 381 | xmpp4r (0.5) |
315 | - xpath (0.1.4) | |
382 | + xpath (1.0.0) | |
316 | 383 | nokogiri (~> 1.3) |
317 | 384 | yajl-ruby (1.1.0) |
318 | 385 | |
... | ... | @@ -321,53 +388,67 @@ PLATFORMS |
321 | 388 | |
322 | 389 | DEPENDENCIES |
323 | 390 | SystemTimer |
324 | - actionmailer_inline_css (~> 1.3.0) | |
391 | + actionmailer_inline_css | |
392 | + airbrake | |
393 | + better_errors | |
394 | + binding_of_caller | |
325 | 395 | bitbucket_rest_api |
326 | - bson (= 1.6.2) | |
327 | - bson_ext (= 1.6.2) | |
328 | - campy | |
396 | + campy (= 0.1.3) | |
329 | 397 | capistrano |
330 | - capybara | |
331 | - database_cleaner (~> 0.6.0) | |
398 | + capybara (~> 2.0.1) | |
399 | + coveralls | |
400 | + database_cleaner (~> 0.9.0) | |
332 | 401 | debugger |
333 | - devise (~> 1.5.3) | |
402 | + decent_exposure | |
403 | + devise | |
334 | 404 | email_spec |
335 | 405 | execjs |
336 | 406 | fabrication (~> 1.3.0) |
407 | + flowdock | |
408 | + foreman | |
409 | + gitlab! | |
337 | 410 | haml |
338 | 411 | hipchat |
339 | 412 | hoi |
340 | 413 | hoptoad_notifier (~> 2.4) |
341 | - htmlentities (~> 4.3.0) | |
342 | - inherited_resources | |
343 | - kaminari | |
414 | + htmlentities | |
415 | + httparty | |
416 | + jira-ruby | |
417 | + jquery-rails (~> 2.1.4) | |
418 | + kaminari (>= 0.14.1) | |
344 | 419 | launchy |
345 | 420 | lighthouse-api |
346 | - mongo (= 1.6.2) | |
347 | - mongoid (~> 2.4.10) | |
348 | - mongoid_rails_migrations | |
349 | - octokit (~> 1.0.0) | |
421 | + meta_request | |
422 | + mongoid (~> 3.1.4) | |
423 | + mongoid-rspec | |
424 | + mongoid_rails_migrations (~> 1.0.1) | |
425 | + octokit | |
350 | 426 | omniauth-github |
351 | 427 | oruen_redmine_client |
352 | 428 | pivotal-tracker |
429 | + pjax_rails | |
353 | 430 | pry-rails |
431 | + quiet_assets | |
354 | 432 | rack-ssl |
355 | 433 | rack-ssl-enforcer |
356 | - rails (= 3.2.8) | |
357 | - rails_autolink (~> 1.0.9) | |
434 | + rails (~> 3.2.13) | |
435 | + rails_autolink | |
358 | 436 | ri_cal |
359 | 437 | rspec-rails (~> 2.6) |
360 | 438 | ruby-debug |
361 | 439 | ruby-fogbugz |
362 | 440 | rushover |
441 | + strong_parameters | |
442 | + taskmapper (~> 0.8.0) | |
443 | + taskmapper-unfuddle (~> 0.7.0) | |
363 | 444 | therubyracer |
364 | 445 | thin |
365 | - timecop | |
446 | + timecop (= 0.6.1) | |
366 | 447 | turbo-sprockets-rails3 |
367 | 448 | uglifier (>= 1.0.3) |
368 | 449 | underscore-rails |
369 | 450 | unicorn |
370 | - useragent (~> 0.3.1) | |
451 | + useragent | |
371 | 452 | webmock |
372 | 453 | xmpp4r |
373 | 454 | yajl-ruby | ... | ... |
LICENSE
1 | -Copyright (c) 2010 Jared Pace | |
1 | +Copyright (c) 2013 errbit team | |
2 | 2 | |
3 | 3 | Permission is hereby granted, free of charge, to any person obtaining |
4 | 4 | a copy of this software and associated documentation files (the |
... | ... | @@ -17,4 +17,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
17 | 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
18 | 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
19 | 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
20 | -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
21 | 20 | \ No newline at end of file |
21 | +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ... | ... |
Procfile
README.md
1 | -# Errbit [![TravisCI][travis-img-url]][travis-ci-url] [![Code Climate][codeclimate-img-url]][codeclimate-url] | |
1 | +# Errbit [![TravisCI][travis-img-url]][travis-ci-url] [![Code Climate][codeclimate-img-url]][codeclimate-url] [![Coveralls][coveralls-img-url]][coveralls-url] [![Dependency Status][gemnasium-img-url]][gemnasium-url] | |
2 | 2 | |
3 | 3 | [travis-img-url]: https://secure.travis-ci.org/errbit/errbit.png?branch=master |
4 | 4 | [travis-ci-url]: http://travis-ci.org/errbit/errbit |
5 | -[codeclimate-img-url]: https://codeclimate.com/badge.png | |
5 | +[codeclimate-img-url]: https://codeclimate.com/github/errbit/errbit.png | |
6 | 6 | [codeclimate-url]: https://codeclimate.com/github/errbit/errbit |
7 | +[coveralls-img-url]: https://coveralls.io/repos/errbit/errbit/badge.png?branch=master | |
8 | +[coveralls-url]:https://coveralls.io/r/errbit/errbit | |
9 | +[gemnasium-img-url]:https://gemnasium.com/errbit/errbit.png | |
10 | +[gemnasium-url]:https://gemnasium.com/errbit/errbit | |
11 | + | |
7 | 12 | |
8 | 13 | |
9 | 14 | ### The open source, self-hosted error catcher |
10 | 15 | |
11 | 16 | |
12 | 17 | Errbit is a tool for collecting and managing errors from other applications. |
13 | -It is [Airbrake](http://airbrakeapp.com) (formerly known as Hoptoad) API compliant, | |
18 | +It is [Airbrake](http://airbrake.io) (formerly known as Hoptoad) API compliant, | |
14 | 19 | so if you are already using Airbrake, you can just point the `airbrake` gem to your Errbit server. |
15 | 20 | |
16 | 21 | |
... | ... | @@ -56,9 +61,9 @@ Errbit may be a good fit for you if: |
56 | 61 | * You want to add customer features to your error catcher |
57 | 62 | * You're crazy and love managing servers |
58 | 63 | |
59 | -If this doesn't sound like you, you should probably stick with [Airbrake](http://airbrakeapp.com). | |
60 | -The [Thoughtbot](http://thoughtbot.com) guys offer great support for it and it is much more worry-free. | |
61 | -They have a free package and even offer a *"Airbrake behind your firewall"* solution. | |
64 | +If this doesn't sound like you, you should probably stick with a hosted service such as | |
65 | +[Airbrake](http://airbrake.io). | |
66 | + | |
62 | 67 | |
63 | 68 | Mailing List |
64 | 69 | ------------ |
... | ... | @@ -73,13 +78,19 @@ There is a demo available at [http://errbit-demo.herokuapp.com/](http://errbit-d |
73 | 78 | Email: demo@errbit-demo.herokuapp.com<br/> |
74 | 79 | Password: password |
75 | 80 | |
81 | +# Requirement | |
82 | + | |
83 | +The list of requirement to install Errbit is : | |
84 | + | |
85 | + * Ruby 1.9.3 or higher | |
86 | + * MongoDB 2.2.0 or higher | |
87 | + | |
76 | 88 | Installation |
77 | 89 | ------------ |
78 | 90 | |
79 | -*Note*: This app is intended for people with experience deploying and maintining | |
91 | +*Note*: This app is intended for people with experience deploying and maintaining | |
80 | 92 | Rails applications. If you're uncomfortable with any step below then Errbit is not |
81 | -for you. Checkout [Airbrake](http://airbrakeapp.com) from the guys over at | |
82 | -[Thoughtbot](http://thoughtbot.com), which Errbit is based on. | |
93 | +for you. | |
83 | 94 | |
84 | 95 | **Set up your local box or server(Ubuntu):** |
85 | 96 | |
... | ... | @@ -87,7 +98,7 @@ for you. Checkout [Airbrake](http://airbrakeapp.com) from the guys over at |
87 | 98 | |
88 | 99 | ```bash |
89 | 100 | apt-get update |
90 | -apt-get install mongodb | |
101 | +apt-get install mongodb-10gen | |
91 | 102 | ``` |
92 | 103 | |
93 | 104 | * Install libxml and libcurl |
... | ... | @@ -124,21 +135,19 @@ rake errbit:bootstrap |
124 | 135 | script/rails server |
125 | 136 | ``` |
126 | 137 | |
127 | -**Deploying:** | |
128 | - | |
129 | - * Bootstrap Errbit. This will copy over config.yml and also seed the database. | |
130 | - | |
131 | -```bash | |
132 | -rake errbit:bootstrap | |
133 | -``` | |
138 | +Deploying: | |
139 | +---------- | |
134 | 140 | |
135 | - * Update the deploy.rb file with information about your server | |
141 | + * Copy `config/deploy.example.rb` to `config/deploy.rb` | |
142 | + * Update the `deploy.rb` or `config.yml` file with information about your server | |
136 | 143 | * Setup server and deploy |
137 | 144 | |
138 | 145 | ```bash |
139 | -cap deploy:setup deploy | |
146 | +cap deploy:setup deploy db:create_mongoid_indexes | |
140 | 147 | ``` |
141 | 148 | |
149 | +(Note: The capistrano deploy script will automatically generate a unique secret token.) | |
150 | + | |
142 | 151 | **Deploying to Heroku:** |
143 | 152 | |
144 | 153 | * Clone the repository |
... | ... | @@ -146,24 +155,32 @@ cap deploy:setup deploy |
146 | 155 | ```bash |
147 | 156 | git clone http://github.com/errbit/errbit.git |
148 | 157 | ``` |
158 | + * Update `db/seeds.rb` with admin credentials for your initial login. | |
159 | + | |
160 | + * Run `bundle` | |
149 | 161 | |
150 | 162 | * Create & configure for Heroku |
151 | 163 | |
152 | 164 | ```bash |
153 | 165 | gem install heroku |
154 | -heroku create example-errbit --stack cedar | |
155 | -heroku addons:add mongolab:starter | |
166 | +heroku create example-errbit | |
167 | +# If you really want, you can define your stack and your buildpack. the default is good to us : | |
168 | +# heroku create example-errbit --stack cedar --buildpack https://github.com/heroku/heroku-buildpack-ruby.git | |
169 | +heroku addons:add mongolab:sandbox | |
156 | 170 | heroku addons:add sendgrid:starter |
157 | 171 | heroku config:add HEROKU=true |
172 | +heroku config:add SECRET_TOKEN="$(bundle exec rake secret)" | |
158 | 173 | heroku config:add ERRBIT_HOST=some-hostname.example.com |
159 | 174 | heroku config:add ERRBIT_EMAIL_FROM=example@example.com |
160 | 175 | git push heroku master |
161 | 176 | ``` |
162 | 177 | |
163 | - * Seed the DB (_NOTE_: No bootstrap task is used on Heroku!) | |
178 | + * Seed the DB (_NOTE_: No bootstrap task is used on Heroku!) and | |
179 | + create index | |
164 | 180 | |
165 | 181 | ```bash |
166 | 182 | heroku run rake db:seed |
183 | +heroku run rake db:mongoid:create_indexes | |
167 | 184 | ``` |
168 | 185 | |
169 | 186 | * If you are using a free database on Heroku, you may want to periodically clear resolved errors to free up space. |
... | ... | @@ -199,6 +216,12 @@ heroku run rake db:seed |
199 | 216 | heroku addons:add deployhooks:http --url="http://YOUR_ERRBIT_HOST/deploys.txt?api_key=YOUR_API_KEY" |
200 | 217 | ``` |
201 | 218 | |
219 | + * You may also want to configure a different secret token for each deploy: | |
220 | + | |
221 | +```bash | |
222 | +heroku config:add SECRET_TOKEN=some-secret-token | |
223 | +``` | |
224 | + | |
202 | 225 | * Enjoy! |
203 | 226 | |
204 | 227 | |
... | ... | @@ -313,6 +336,7 @@ When upgrading Errbit, please run: |
313 | 336 | git pull origin master # assuming origin is the github.com/errbit/errbit repo |
314 | 337 | bundle install |
315 | 338 | rake db:migrate |
339 | +rake assets:precompile | |
316 | 340 | ``` |
317 | 341 | |
318 | 342 | If we change the way that data is stored, this will run any migrations to bring your database up to date. |
... | ... | @@ -340,6 +364,20 @@ it will be displayed under the *User Details* tab: |
340 | 364 | |
341 | 365 | (This tab will be hidden if no user information is available.) |
342 | 366 | |
367 | +Adding javascript errors notifications | |
368 | +-------------------------------------- | |
369 | + | |
370 | +Errbit easily supports javascript errors notifications. You just need to add `config.js_notifier = true` to the errbit initializer in the rails app. | |
371 | + | |
372 | +``` | |
373 | +Errbit.configure do |config| | |
374 | + config.host = 'YOUR-ERRBIT-HOST' | |
375 | + config.api_key = 'YOUR-PROJECT-API-KEY' | |
376 | + config.js_notifier = true | |
377 | +end | |
378 | +``` | |
379 | + | |
380 | +Then get the `notifier.js` from `errbit/public/javascript/notifier.js` and add to `application.js` on your rails app or include `http://YOUR-ERRBIT-HOST/javascripts/notifier.js` on your `application.html.erb.` | |
343 | 381 | |
344 | 382 | Issue Trackers |
345 | 383 | -------------- |
... | ... | @@ -387,9 +425,35 @@ card_type = Defect, status = Open, priority = Essential |
387 | 425 | |
388 | 426 | * Account is the host of your gitlab installation. i.e. **http://gitlab.example.com** |
389 | 427 | * To authenticate, Errbit uses token-based authentication. Get your API Key in your user settings (or create special user for this purpose) |
390 | -* You also need to provide project name (shortname) or ID (number) for issues to be created | |
391 | -* **Currently (as of 3.0), Gitlab has 2000 character limit for issue description.** It is necessary to turn it off at your instance, because Errbit issues body is much longer. Please comment validation line in issue model in models folder https://github.com/gitlabhq/gitlabhq/blob/master/app/models/issue.rb#L10 | |
428 | +* You also need to provide project ID (it needs to be Number) for issues to be created | |
429 | + | |
430 | +**Unfuddle Issues Integration** | |
431 | + | |
432 | +* Account is your unfuddle domain | |
433 | +* Username your unfuddle username | |
434 | +* Password your unfuddle password | |
435 | +* Project id the id of your project where your ticket is create | |
436 | +* Milestone id the id of your milestone where your ticket is create | |
437 | + | |
438 | +**Jira Issue Integration** | |
439 | + | |
440 | +* base_url the jira URL | |
441 | +* context_path Context Path (Just "/" if empty otherwise with leading slash) | |
442 | +* username HTTP Basic Auth User | |
443 | +* password HTTP Basic Auth Password | |
444 | +* project_id The project Key where the issue will be created | |
445 | +* account Assign to this user. If empty, Jira takes the project default. | |
446 | +* issue_component Website - Other | |
447 | +* issue_type Issue type | |
448 | +* issue_priority Priority | |
449 | + | |
450 | +Notification Service | |
451 | +-------------------- | |
392 | 452 | |
453 | +**Flowdock Notification** | |
454 | + | |
455 | +Allow notification to [Flowdock](https://www.flowdock.com/). See | |
456 | +[complete documentation](docs/notifications/flowdock/index.md) | |
393 | 457 | |
394 | 458 | |
395 | 459 | What if Errbit has an error? |
... | ... | @@ -434,12 +498,28 @@ Solutions known to work are listed below: |
434 | 498 | </tr> |
435 | 499 | </table> |
436 | 500 | |
501 | +Develop on Errbit | |
502 | +----------------- | |
503 | + | |
504 | +A guide can help on this way on [**Errbit Advanced Developer Guide**](docs/DEVELOPER-ADVANCED.md) | |
505 | + | |
506 | +## Other documentation | |
507 | + | |
508 | +* [All ENV variables availables to configure Errbit](docs/ENV-VARIABLES.md) | |
509 | + | |
437 | 510 | TODO |
438 | 511 | ---- |
439 | 512 | |
440 | 513 | * Add ability for watchers to be configured for types of notifications they should receive |
441 | 514 | |
442 | 515 | |
516 | +People using Errbit | |
517 | +------------------- | |
518 | + | |
519 | +See our wiki page for a [list of people and companies around the world who use Errbit](https://github.com/errbit/errbit/wiki/People-using-Errbit). | |
520 | +Feel free to [edit this page](https://github.com/errbit/errbit/wiki/People-using-Errbit/_edit), and add your name and country to the list if you are using Errbit. | |
521 | + | |
522 | + | |
443 | 523 | Special Thanks |
444 | 524 | -------------- |
445 | 525 | |
... | ... | @@ -448,10 +528,11 @@ Special Thanks |
448 | 528 | * [Nathan Broadbent (@ndbroadbent)](https://github.com/ndbroadbent) - Maintaining Errbit and contributing many features |
449 | 529 | * [Vasiliy Ermolovich (@nashby)](https://github.com/nashby) - Contributing and helping to resolve issues and pull requests |
450 | 530 | * [Marcin Ciunelis (@martinciu)](https://github.com/martinciu) - Helping to improve Errbit's architecture |
531 | +* [Cyril Mougel (@shingara)](https://github.com/shingara) - Maintaining Errbit and contributing many features | |
451 | 532 | * [Relevance](http://thinkrelevance.com) - For giving me Open-source Fridays to work on Errbit and all my awesome co-workers for giving feedback and inspiration. |
452 | -* [Thoughtbot](http://thoughtbot.com) - For being great open-source advocates and setting the bar with [Airbrake](http://airbrakeapp.com). | |
533 | +* [Thoughtbot](http://thoughtbot.com) - For being great open-source advocates and setting the bar with [Airbrake](http://airbrake.io). | |
453 | 534 | |
454 | -See the [contributors graph](https://github.com/errbit/errbit/graphs/contributors) for further details. | |
535 | +See the [contributors graph](https://github.com/errbit/errbit/graphs/contributors) for further details. You can see another list of Contributors by release version on [CONTRIBUTORS.md] | |
455 | 536 | |
456 | 537 | |
457 | 538 | Contributing |
... | ... | @@ -474,10 +555,11 @@ and make **optional** features configurable via `config/config.yml`. |
474 | 555 | * Add tests for it. This is important so we don't break it in a future version unintentionally. |
475 | 556 | * Commit, do not mess with Rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself we can ignore when we pull) |
476 | 557 | * Send us a pull request. Bonus points for topic branches. |
558 | +* Add you on the CONTRIBUTORS.md file on the current release | |
477 | 559 | |
478 | 560 | |
479 | 561 | Copyright |
480 | 562 | --------- |
481 | 563 | |
482 | -Copyright (c) 2010-2011 Jared Pace. See LICENSE for details. | |
564 | +Copyright (c) 2010-2013 Errbit Team. See LICENSE for details. | |
483 | 565 | ... | ... |
1.18 KB
1.18 KB
1.1 KB
4.96 KB
4.96 KB
4.78 KB
1.97 KB
1.97 KB
1.97 KB
1.91 KB
2.29 KB
2.29 KB
2.11 KB
3.16 KB
3.16 KB
2.49 KB
app/assets/javascripts/application.js.erb
app/assets/javascripts/errbit.js
... | ... | @@ -14,38 +14,28 @@ $(function() { |
14 | 14 | |
15 | 15 | bindRequiredPasswordMarks(); |
16 | 16 | |
17 | - $('#watcher_name').live("click", function() { | |
18 | - $(this).closest('form').find('.show').removeClass('show'); | |
19 | - $('#app_watchers_attributes_0_user_id').addClass('show'); | |
20 | - }); | |
21 | - | |
22 | - $('#watcher_email').live("click", function() { | |
23 | - $(this).closest('form').find('.show').removeClass('show'); | |
24 | - $('#app_watchers_attributes_0_email').addClass('show'); | |
25 | - }); | |
26 | - | |
27 | - $('a.copy_config').live("click", function() { | |
17 | + // On page apps/:app_id/edit | |
18 | + $('a.copy_config').on("click", function() { | |
28 | 19 | $('select.choose_other_app').show().focus(); |
29 | 20 | }); |
30 | 21 | |
31 | - $('select.choose_other_app').live("change", function() { | |
22 | + $('select.choose_other_app').on("change", function() { | |
32 | 23 | var loc = window.location; |
33 | 24 | window.location.href = loc.protocol + "//" + loc.host + loc.pathname + |
34 | 25 | "?copy_attributes_from=" + $(this).val(); |
35 | 26 | }); |
36 | 27 | |
37 | - $('input[type=submit][data-action]').click(function() { | |
28 | + $('input[type=submit][data-action]').live('click', function() { | |
38 | 29 | $(this).closest('form').attr('action', $(this).attr('data-action')); |
39 | 30 | }); |
40 | 31 | |
41 | 32 | $('.notice-pagination').each(function() { |
42 | - $('.notice-pagination a').pjax('#content', { timeout: 2000}); | |
43 | - $('#content').bind('pjax:start', function() { | |
44 | - $('.notice-pagination-loader').css("visibility", "visible"); | |
45 | - currentTab = $('.tab-bar ul li a.button.active').attr('rel'); | |
46 | - }); | |
33 | + $.pjax.defaults = {timeout: 2000}; | |
47 | 34 | |
48 | - $('#content').bind('pjax:end', function() { | |
35 | + $('#content').pjax('.notice-pagination a').on('pjax:start', function() { | |
36 | + $('.notice-pagination-loader').css("visibility", "visible"); | |
37 | + currentTab = $('.tab-bar ul li a.button.active').attr('rel'); | |
38 | + }).on('pjax:end', function() { | |
49 | 39 | activateTabbedPanels(); |
50 | 40 | }); |
51 | 41 | }); |
... | ... | @@ -83,7 +73,7 @@ $(function() { |
83 | 73 | function toggleProblemsCheckboxes() { |
84 | 74 | var checkboxToggler = $('#toggle_problems_checkboxes'); |
85 | 75 | |
86 | - checkboxToggler.live("click", function() { | |
76 | + checkboxToggler.on("click", function() { | |
87 | 77 | $('input[name^="problems"]').each(function() { |
88 | 78 | this.checked = checkboxToggler.get(0).checked; |
89 | 79 | }); |
... | ... | @@ -126,7 +116,7 @@ $(function() { |
126 | 116 | $('td.backtrace_separator').hide(); |
127 | 117 | } |
128 | 118 | // Show external backtrace lines when clicking separator |
129 | - $('td.backtrace_separator span').live('click', show_external_backtrace); | |
119 | + $('td.backtrace_separator span').on('click', show_external_backtrace); | |
130 | 120 | // Hide external backtrace on page load |
131 | 121 | hide_external_backtrace(); |
132 | 122 | ... | ... |
app/assets/javascripts/form.js
... | ... | @@ -19,7 +19,8 @@ $(function(){ |
19 | 19 | }); |
20 | 20 | |
21 | 21 | function activateNestedForms() { |
22 | - $('.nested-wrapper').each(function(){ | |
22 | + var wrapper = $('.nested-wrapper') | |
23 | + wrapper.each(function(){ | |
23 | 24 | var wrapper = $(this); |
24 | 25 | |
25 | 26 | makeNestedItemsDestroyable(wrapper); |
... | ... | @@ -28,7 +29,7 @@ function activateNestedForms() { |
28 | 29 | addLink.click(appendNestedItem); |
29 | 30 | wrapper.append(addLink); |
30 | 31 | }); |
31 | - $('.nested a.remove-nested').live('click',removeNestedItem); | |
32 | + wrapper.on('click','.nested a.remove-nested', removeNestedItem); | |
32 | 33 | } |
33 | 34 | |
34 | 35 | function makeNestedItemsDestroyable(wrapper) { |
... | ... | @@ -80,7 +81,7 @@ function activateTypeSelector(field_class, section_class) { |
80 | 81 | $('div.'+field_class+' > div.'+section_class).not('.chosen').find('input') |
81 | 82 | .attr('disabled','disabled').val(''); |
82 | 83 | |
83 | - $('div.'+field_class+' input[name*=type]').live('click', function(){ | |
84 | + $('div.'+field_class+' input[name*=type]').on('click', function(){ | |
84 | 85 | // Look for section in 'data-section', and fall back to 'value' |
85 | 86 | var chosen = $(this).data("section") || $(this).val(); |
86 | 87 | var wrapper = $(this).closest('.nested'); | ... | ... |
app/assets/javascripts/jquery.alerts.js
... | ... | @@ -12,7 +12,7 @@ |
12 | 12 | // $.jAlert( message, [title, callback] ) |
13 | 13 | // $.jConfirm( message, [title, callback] ) |
14 | 14 | // $.jPrompt( message, [value, title, callback] ) |
15 | -// | |
15 | +// | |
16 | 16 | // History: |
17 | 17 | // |
18 | 18 | // 1.00 - Released (29 December 2008) |
... | ... | @@ -22,16 +22,16 @@ |
22 | 22 | // 1.2 - global methods removed. |
23 | 23 | // |
24 | 24 | // License: |
25 | -// | |
25 | +// | |
26 | 26 | // This plugin is dual-licensed under the GNU General Public License and the MIT License and |
27 | -// is copyright 2008 A Beautiful Site, LLC. | |
27 | +// is copyright 2008 A Beautiful Site, LLC. | |
28 | 28 | // |
29 | 29 | (function($) { |
30 | - | |
30 | + | |
31 | 31 | $.alerts = { |
32 | - | |
32 | + | |
33 | 33 | // These properties can be read/written by accessing $.alerts.propertyName from your scripts at any time |
34 | - | |
34 | + | |
35 | 35 | verticalOffset: -75, // vertical offset of the dialog from center screen, in pixels |
36 | 36 | horizontalOffset: 0, // horizontal offset of the dialog from center screen, in pixels/ |
37 | 37 | repositionOnResize: true, // re-centers the dialog on window resize |
... | ... | @@ -46,37 +46,37 @@ |
46 | 46 | confirm: 'Confirm', |
47 | 47 | prompt: 'Prompt' |
48 | 48 | }, |
49 | - | |
49 | + | |
50 | 50 | // Public methods |
51 | - | |
51 | + | |
52 | 52 | alert: function(message, title, callback) { |
53 | 53 | if (! title) title = $.alerts.titles.alert; |
54 | 54 | $.alerts._show(title, message, null, 'alert', function(result) { |
55 | 55 | if (callback) callback(result); |
56 | 56 | }); |
57 | 57 | }, |
58 | - | |
58 | + | |
59 | 59 | confirm: function(message, title, callback) { |
60 | 60 | if (! title) title = $.alerts.titles.confirm; |
61 | 61 | $.alerts._show(title, message, null, 'confirm', function(result) { |
62 | 62 | if (callback) callback(result); |
63 | 63 | }); |
64 | 64 | }, |
65 | - | |
65 | + | |
66 | 66 | prompt: function(message, value, title, callback) { |
67 | 67 | if (! title) title = $.alerts.titles.prompt; |
68 | 68 | $.alerts._show(title, message, value, 'prompt', function(result) { |
69 | 69 | if(callback) callback(result); |
70 | 70 | }); |
71 | 71 | }, |
72 | - | |
72 | + | |
73 | 73 | // Private methods |
74 | - | |
74 | + | |
75 | 75 | _show: function(title, msg, value, type, callback) { |
76 | - | |
76 | + | |
77 | 77 | $.alerts._hide(); |
78 | 78 | $.alerts._overlay('show'); |
79 | - | |
79 | + | |
80 | 80 | $("BODY").append( |
81 | 81 | '<div id="popup_container">' + |
82 | 82 | '<h1 id="popup_title"></h1>' + |
... | ... | @@ -84,32 +84,34 @@ |
84 | 84 | '<div id="popup_message"></div>' + |
85 | 85 | '</div>' + |
86 | 86 | '</div>'); |
87 | - | |
87 | + | |
88 | 88 | if( $.alerts.dialogClass ) $("#popup_container").addClass($.alerts.dialogClass); |
89 | - | |
89 | + | |
90 | 90 | // IE6 Fix |
91 | - var pos = ($.browser.msie && parseInt($.browser.version, 10) <= 6 ) ? 'absolute' : 'fixed'; | |
92 | - | |
91 | + // No more $.browser in Jquery > 1,9 and not support IE 6 | |
92 | + // var pos = ($.browser.msie && parseInt($.browser.version, 10) <= 6 ) ? 'absolute' : 'fixed'; | |
93 | + var pos = 'fixed'; | |
94 | + | |
93 | 95 | $("#popup_container").css({ |
94 | 96 | position: pos, |
95 | 97 | zIndex: 99999, |
96 | 98 | padding: 0, |
97 | 99 | margin: 0 |
98 | 100 | }); |
99 | - | |
101 | + | |
100 | 102 | $("#popup_title").text(title); |
101 | 103 | $("#popup_content").addClass(type); |
102 | 104 | $("#popup_message").text(msg); |
103 | 105 | $("#popup_message").html( $("#popup_message").text().replace(/\n/g, '<br />') ); |
104 | - | |
106 | + | |
105 | 107 | $("#popup_container").css({ |
106 | 108 | minWidth: $("#popup_container").outerWidth(), |
107 | 109 | maxWidth: $("#popup_container").outerWidth() |
108 | 110 | }); |
109 | - | |
111 | + | |
110 | 112 | $.alerts._reposition(); |
111 | 113 | $.alerts._maintainPosition(true); |
112 | - | |
114 | + | |
113 | 115 | switch( type ) { |
114 | 116 | case 'alert': |
115 | 117 | $("#popup_message").after('<div id="popup_panel"><input type="button" value="' + $.alerts.okButton + '" id="popup_ok" /></div>'); |
... | ... | @@ -158,20 +160,20 @@ |
158 | 160 | break; |
159 | 161 | default: break; |
160 | 162 | } |
161 | - | |
163 | + | |
162 | 164 | // Make draggable |
163 | 165 | if ($.alerts.draggable && $.fn.draggable) { |
164 | 166 | $("#popup_container").draggable({ handle: $("#popup_title") }); |
165 | 167 | $("#popup_title").css({ cursor: 'move' }); |
166 | 168 | } |
167 | 169 | }, |
168 | - | |
170 | + | |
169 | 171 | _hide: function() { |
170 | 172 | $("#popup_container").remove(); |
171 | 173 | $.alerts._overlay('hide'); |
172 | 174 | $.alerts._maintainPosition(false); |
173 | 175 | }, |
174 | - | |
176 | + | |
175 | 177 | _overlay: function(status) { |
176 | 178 | switch( status ) { |
177 | 179 | case 'show': |
... | ... | @@ -194,23 +196,23 @@ |
194 | 196 | default: break; |
195 | 197 | } |
196 | 198 | }, |
197 | - | |
199 | + | |
198 | 200 | _reposition: function() { |
199 | 201 | var top = (($(window).height() / 2) - ($("#popup_container").outerHeight() / 2)) + $.alerts.verticalOffset; |
200 | 202 | var left = (($(window).width() / 2) - ($("#popup_container").outerWidth() / 2)) + $.alerts.horizontalOffset; |
201 | 203 | if( top < 0 ) top = 0; |
202 | 204 | if( left < 0 ) left = 0; |
203 | - | |
205 | + | |
204 | 206 | // IE6 fix |
205 | - if( $.browser.msie && parseInt($.browser.version, 10) <= 6 ) top = top + $(window).scrollTop(); | |
206 | - | |
207 | + // if( $.browser.msie && parseInt($.browser.version, 10) <= 6 ) top = top + $(window).scrollTop(); | |
208 | + | |
207 | 209 | $("#popup_container").css({ |
208 | 210 | top: top + 'px', |
209 | 211 | left: left + 'px' |
210 | 212 | }); |
211 | 213 | $("#popup_overlay").height( $(document).height() ); |
212 | 214 | }, |
213 | - | |
215 | + | |
214 | 216 | _maintainPosition: function(status) { |
215 | 217 | if( $.alerts.repositionOnResize ) { |
216 | 218 | switch(status) { |
... | ... | @@ -224,7 +226,7 @@ |
224 | 226 | } |
225 | 227 | } |
226 | 228 | } |
227 | - | |
229 | + | |
228 | 230 | }; |
229 | - | |
230 | -})(jQuery); | |
231 | 231 | \ No newline at end of file |
232 | + | |
233 | +})(jQuery); | ... | ... |
app/assets/javascripts/jquery.js
... | ... | @@ -1,18 +0,0 @@ |
1 | -/*! | |
2 | - * jQuery JavaScript Library v1.6.2 | |
3 | - * http://jquery.com/ | |
4 | - * | |
5 | - * Copyright 2011, John Resig | |
6 | - * Dual licensed under the MIT or GPL Version 2 licenses. | |
7 | - * http://jquery.org/license | |
8 | - * | |
9 | - * Includes Sizzle.js | |
10 | - * http://sizzlejs.com/ | |
11 | - * Copyright 2011, The Dojo Foundation | |
12 | - * Released under the MIT, BSD, and GPL Licenses. | |
13 | - * | |
14 | - * Date: Thu Jun 30 14:16:56 2011 -0400 | |
15 | - */ | |
16 | -(function(a,b){function cv(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cs(a){if(!cg[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"<!doctype html>":"")+"<html><body>"),ci.close();d=ci.createElement(a),ci.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ch)}cg[a]=e}return cg[a]}function cr(a,b){var c={};f.each(cm.concat.apply([],cm.slice(0,b)),function(){c[this]=a});return c}function cq(){cn=b}function cp(){setTimeout(cq,0);return cn=f.now()}function cf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ce(){try{return new a.XMLHttpRequest}catch(b){}}function b$(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function bZ(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function bY(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bC.test(a)?d(a,e):bY(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)bY(a+"["+e+"]",b[e],c,d);else d(a,b)}function bX(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bR,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=bX(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=bX(a,c,d,e,"*",g));return l}function bW(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bN),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bA(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?bv:bw;if(d>0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bx(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function bm(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(be,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bl(a){f.nodeName(a,"input")?bk(a):"getElementsByTagName"in a&&f.grep(a.getElementsByTagName("input"),bk)}function bk(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bj(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bi(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bh(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i<j;i++)f.event.add(b,h+(g[h][i].namespace?".":"")+g[h][i].namespace,g[h][i],g[h][i].data)}}}}function bg(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function W(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(R.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function V(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function N(a,b){return(a&&a!=="*"?a+".":"")+b.replace(z,"`").replace(A,"&")}function M(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;i<s.length;i++)g=s[i],g.origType.replace(x,"")===a.type?q.push(g.selector):s.splice(i--,1);e=f(a.target).closest(q,a.currentTarget);for(j=0,k=e.length;j<k;j++){m=e[j];for(i=0;i<s.length;i++){g=s[i];if(m.selector===g.selector&&(!n||n.test(g.namespace))&&!m.elem.disabled){h=m.elem,d=null;if(g.preType==="mouseenter"||g.preType==="mouseleave")a.type=g.preType,d=f(a.relatedTarget).closest(g.selector)[0],d&&f.contains(h,d)&&(d=h);(!d||d!==h)&&p.push({elem:h,handleObj:g,level:m.level})}}}for(j=0,k=p.length;j<k;j++){e=p[j];if(c&&e.level>c)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function K(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function E(){return!0}function D(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z])/ig,x=function(a,b){return b.toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;A.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!A){A=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||D.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b){if(H)return H.call(b,a);for(var c=0,d=b.length;c<d;c++)if(b[c]===a)return c;return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=s.exec(a)||t.exec(a)||u.exec(a)||a.indexOf("compatible")<0&&v.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g="done fail isResolved isRejected promise then always pipe".split(" "),h=[].slice;f.extend({_Deferred:function(){var a=[],b,c,d,e={done:function(){if(!d){var c=arguments,g,h,i,j,k;b&&(k=b,b=0);for(g=0,h=c.length;g<h;g++)i=c[g],j=f.type(i),j==="array"?e.done.apply(e,i):j==="function"&&a.push(i);k&&e.resolveWith(k[0],k[1])}return this},resolveWith:function(e,f){if(!d&&!b&&!c){f=f||[],c=1;try{while(a[0])a.shift().apply(e,f)}finally{b=[e,f],c=0}}return this},resolve:function(){e.resolveWith(this,arguments);return this},isResolved:function(){return!!c||!!b},cancel:function(){d=1,a=[];return this}};return e},Deferred:function(a){var b=f._Deferred(),c=f._Deferred(),d;f.extend(b,{then:function(a,c){b.done(a).fail(c);return this},always:function(){return b.done.apply(b,arguments).fail.apply(this,arguments)},fail:c.done,rejectWith:c.resolveWith,reject:c.resolve,isRejected:c.isResolved,pipe:function(a,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[c,"reject"]},function(a,c){var e=c[0],g=c[1],h;f.isFunction(e)?b[a](function(){h=e.apply(this,arguments),h&&f.isFunction(h.promise)?h.promise().then(d.resolve,d.reject):d[g](h)}):b[a](d[g])})}).promise()},promise:function(a){if(a==null){if(d)return d;d=a={}}var c=g.length;while(c--)a[g[c]]=b[g[c]];return a}}),b.done(c.cancel).fail(b.cancel),delete b.cancel,a&&a.call(b,b);return b},when:function(a){function i(a){return function(c){b[a]=arguments.length>1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c<d;c++)b[c]&&f.isFunction(b[c].promise)?b[c].promise().then(i(c),g.reject):--e;e||g.resolveWith(g,b)}else g!==a&&g.resolveWith(g,d?[a]:[]);return g.promise()}}),f.support=function(){var a=c.createElement("div"),b=c.documentElement,d,e,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u;a.setAttribute("className","t"),a.innerHTML=" <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.firstChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0},m&&f.extend(p,{position:"absolute",left:-1e3,top:-1e3});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="<div style='width:4px;'></div>",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",q=a.getElementsByTagName("td"),u=q[0].offsetHeight===0,q[0].style.display="",q[1].style.display="none",k.reliableHiddenOffsets=u&&q[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",a.appendChild(j),k.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0),o.innerHTML="",n.removeChild(o);if(a.attachEvent)for(t in{submit:1,change:1,focusin:1})s="on"+t,u=s in a,u||(a.setAttribute(s,"return;"),u=typeof a[s]=="function"),k[t+"Bubbles"]=u;o=l=g=h=m=j=a=i=null;return k}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g=f.expando,h=typeof c=="string",i,j=a.nodeType,k=j?f.cache:a,l=j?a[f.expando]:a[f.expando]&&f.expando;if((!l||e&&l&&!k[l][g])&&h&&d===b)return;l||(j?a[f.expando]=l=++f.uuid:l=f.expando),k[l]||(k[l]={},j||(k[l].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?k[l][g]=f.extend(k[l][g],c):k[l]=f.extend(k[l],c);i=k[l],e&&(i[g]||(i[g]={}),i=i[g]),d!==b&&(i[f.camelCase(c)]=d);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[f.camelCase(c)]||i[c]:i}},removeData:function(b,c,d){if(!!f.acceptData(b)){var e=f.expando,g=b.nodeType,h=g?f.cache:b,i=g?b[f.expando]:f.expando;if(!h[i])return;if(c){var j=d?h[i][e]:h[i];if(j){delete j[c];if(!l(j))return}}if(d){delete h[i][e];if(!l(h[i]))return}var k=h[i][e];f.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=k):g&&(f.support.deleteExpando?delete b[f.expando]:b.removeAttribute?b.removeAttribute(f.expando):b[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h<i;h++)g=e[h].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),k(this[0],g,d[g]))}}return d}if(typeof a=="object")return this.each(function(){f.data(this,a)});var j=a.split(".");j[1]=j[1]?"."+j[1]:"";if(c===b){d=this.triggerHandler("getData"+j[1]+"!",[j[0]]),d===b&&this.length&&(d=f.data(this[0],a),d=k(this[0],a,d));return d===b&&j[1]?this.data(j[0]):d}return this.each(function(){var b=f(this),d=[j[0],c];b.triggerHandler("setData"+j[1]+"!",d),f.data(this,a,c),b.triggerHandler("changeData"+j[1]+"!",d)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,c){a&&(c=(c||"fx")+"mark",f.data(a,c,(f.data(a,c,b,!0)||0)+1,!0))},_unmark:function(a,c,d){a!==!0&&(d=c,c=a,a=!1);if(c){d=d||"fx";var e=d+"mark",g=a?0:(f.data(c,e,b,!0)||1)-1;g?f.data(c,e,g,!0):(f.removeData(c,e,!0),m(c,d,"mark"))}},queue:function(a,c,d){if(a){c=(c||"fx")+"queue";var e=f.data(a,c,b,!0);d&&(!e||f.isArray(d)?e=f.data(a,c,f.makeArray(d),!0):e.push(d));return e||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e;d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),d.call(a,function(){f.dequeue(a,b)})),c.length||(f.removeData(a,b+"queue",!0),m(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(){var c=this;setTimeout(function(){f.dequeue(c,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f._Deferred(),!0))h++,l.done(m);m();return d.promise()}});var n=/[\n\t\r]/g,o=/\s+/,p=/\r/g,q=/^(?:button|input)$/i,r=/^(?:button|input|object|select|textarea)$/i,s=/^a(?:rea)?$/i,t=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,u=/\:|^on/,v,w;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(o);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(o);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(n," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(o);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ";for(var c=0,d=this.length;c<d;c++)if((" "+this[c].className+" ").replace(n," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;d=e.value;return typeof d=="string"?d.replace(p,""):d==null?"":d}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h<i;h++){var j=e[h];if(j.selected&&(f.support.optDisabled?!j.disabled:j.getAttribute("disabled")===null)&&(!j.parentNode.disabled||!f.nodeName(j.parentNode,"optgroup"))){b=f(j).val();if(g)return b;d.push(b)}}if(g&&!d.length&&e.length)return f(e[c]).val();return d},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);j&&(c=f.attrFix[c]||c,i=f.attrHooks[c],i||(t.test(c)?i=w:v&&c!=="className"&&(f.nodeName(a,"form")||u.test(c))&&(i=v)));if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j&&(h=i.get(a,c))!==null)return h;h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.support.getSetAttribute?a.removeAttribute(b):(f.attr(a,b,""),a.removeAttributeNode(a.getAttributeNode(b))),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},tabIndex:{get:function(a){var c=a.getAttributeNode("tabIndex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}},value:{get:function(a,b){if(v&&f.nodeName(a,"button"))return v.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(v&&f.nodeName(a,"button"))return v.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);i&&(c=f.propFix[c]||c,h=f.propHooks[c]);return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==b?g:a[c]},propHooks:{}}),w={get:function(a,c){return f.prop(a,c)?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},f.support.getSetAttribute||(f.attrFix=f.propFix,v=f.attrHooks.name=f.attrHooks.title=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,c){var d=a.getAttributeNode(c);if(d){d.nodeValue=b;return b}}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var x=/\.(.*)$/,y=/^(?:textarea|input|select)$/i,z=/\./g,A=/ /g,B=/[^\w\s.|`]/g,C=function(a){return a.replace(B,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=D;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=D);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),C).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j<p.length;j++){q=p[j];if(l||n.test(q.namespace))f.event.remove(a,r,q.handler,j),p.splice(j--,1)}continue}o=f.event.special[h]||{};for(j=e||0;j<p.length;j++){q=p[j];if(d.guid===q.guid){if(l||n.test(q.namespace))e==null&&p.splice(j--,1),o.remove&&o.remove.call(a,q);if(e!=null)break}}if(p.length===0||e!=null&&p.length===1)(!o.teardown||o.teardown.call(a,m)===!1)&&f.removeEvent(a,h,s.handle),g=null,delete t[h]}if(f.isEmptyObject(t)){var u=s.handle;u&&(u.elem=null),delete s.events,delete s.handle,f.isEmptyObject(s)&&f.removeData(a,b,!0)}}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){var h=c.type||c,i=[],j;h.indexOf("!")>=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i. | |
17 | -shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d!=null?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h<i;h++){var j=d[h];if(e||c.namespace_re.test(j.namespace)){c.handler=j.handler,c.data=j.data,c.handleObj=j;var k=j.handler.apply(this,g);k!==b&&(c.result=k,k===!1&&(c.preventDefault(),c.stopPropagation()));if(c.isImmediatePropagationStopped())break}}return c.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(a){if(a[f.expando])return a;var d=a;a=f.Event(d);for(var e=this.props.length,g;e;)g=this.props[--e],a[g]=d[g];a.target||(a.target=a.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),!a.relatedTarget&&a.fromElement&&(a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement);if(a.pageX==null&&a.clientX!=null){var h=a.target.ownerDocument||c,i=h.documentElement,j=h.body;a.pageX=a.clientX+(i&&i.scrollLeft||j&&j.scrollLeft||0)-(i&&i.clientLeft||j&&j.clientLeft||0),a.pageY=a.clientY+(i&&i.scrollTop||j&&j.scrollTop||0)-(i&&i.clientTop||j&&j.clientTop||0)}a.which==null&&(a.charCode!=null||a.keyCode!=null)&&(a.which=a.charCode!=null?a.charCode:a.keyCode),!a.metaKey&&a.ctrlKey&&(a.metaKey=a.ctrlKey),!a.which&&a.button!==b&&(a.which=a.button&1?1:a.button&2?3:a.button&4?2:0);return a},guid:1e8,proxy:f.proxy,special:{ready:{setup:f.bindReady,teardown:f.noop},live:{add:function(a){f.event.add(this,N(a.origType,a.selector),f.extend({},a,{handler:M,guid:a.handler.guid}))},remove:function(a){f.event.remove(this,N(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}}},f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!this.preventDefault)return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?E:D):this.type=a,b&&f.extend(this,b),this.timeStamp=f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=E;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=E;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=E,this.stopPropagation()},isDefaultPrevented:D,isPropagationStopped:D,isImmediatePropagationStopped:D};var F=function(a){var b=a.relatedTarget,c=!1,d=a.type;a.type=a.data,b!==this&&(b&&(c=f.contains(this,b)),c||(f.event.handle.apply(this,arguments),a.type=d))},G=function(a){a.type=a.data,f.event.handle.apply(this,arguments)};f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={setup:function(c){f.event.add(this,b,c&&c.selector?G:F,a)},teardown:function(a){f.event.remove(this,b,a&&a.selector?G:F)}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(a,b){if(!f.nodeName(this,"form"))f.event.add(this,"click.specialSubmit",function(a){var b=a.target,c=b.type;(c==="submit"||c==="image")&&f(b).closest("form").length&&K("submit",this,arguments)}),f.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,c=b.type;(c==="text"||c==="password")&&f(b).closest("form").length&&a.keyCode===13&&K("submit",this,arguments)});else return!1},teardown:function(a){f.event.remove(this,".specialSubmit")}});if(!f.support.changeBubbles){var H,I=function(a){var b=a.type,c=a.value;b==="radio"||b==="checkbox"?c=a.checked:b==="select-multiple"?c=a.selectedIndex>-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},J=function(c){var d=c.target,e,g;if(!!y.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=I(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:J,beforedeactivate:J,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&J.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&J.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",I(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in H)f.event.add(this,c+".specialChange",H[c]);return y.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return y.test(this.nodeName)}},H=f.event.special.change.filters,H.focus=H.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i<j;i++)f.event.add(this[i],a,g,d);return this}}),f.fn.extend({unbind:function(a,b){if(typeof a=="object"&&!a.preventDefault)for(var c in a)this.unbind(c,a[c]);else for(var d=0,e=this.length;d<e;d++)f.event.remove(this[d],a,b);return this},delegate:function(a,b,c,d){return this.live(b,c,d,a)},undelegate:function(a,b,c){return arguments.length===0?this.unbind("live"):this.die(b,null,c,a)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f.data(this,"lastToggle"+a.guid)||0)%d;f.data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var L={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};f.each(["live","die"],function(a,c){f.fn[c]=function(a,d,e,g){var h,i=0,j,k,l,m=g||this.selector,n=g?this:f(this.context);if(typeof a=="object"&&!a.preventDefault){for(var o in a)n[c](o,d,a[o],m);return this}if(c==="die"&&!a&&g&&g.charAt(0)==="."){n.unbind(g);return this}if(d===!1||f.isFunction(d))e=d||D,d=b;a=(a||"").split(" ");while((h=a[i++])!=null){j=x.exec(h),k="",j&&(k=j[0],h=h.replace(x,""));if(h==="hover"){a.push("mouseenter"+k,"mouseleave"+k);continue}l=h,L[h]?(a.push(L[h]+k),h=h+k):h=(L[h]||h)+k;if(c==="live")for(var p=0,q=n.length;p<q;p++)f.event.add(n[p],"live."+N(h,m),{data:d,selector:m,handler:e,origType:h,origHandler:e,preType:l});else n.unbind("live."+N(h,m),e)}return this}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}if(i.nodeType===1){f||(i.sizcache=c,i.sizset=g);if(typeof b!="string"){if(i===b){j=!0;break}}else if(k.filter(b,[i]).length>0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}i.nodeType===1&&!f&&(i.sizcache=c,i.sizset=g);if(i.nodeName.toLowerCase()===b){j=i;break}i=i[a]}d[g]=j}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},k.matches=function(a,b){return k(a,null,null,b)},k.matchesSelector=function(a,b){return k(b,null,null,[a]).length>0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e<f;e++){var g,h=l.order[e];if(g=l.leftMatch[h].exec(a)){var j=g[1];g.splice(1,1);if(j.substr(j.length-1)!=="\\"){g[1]=(g[1]||"").replace(i,""),d=l.find[h](g,b,c);if(d!=null){a=a.replace(l.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},k.filter=function(a,c,d,e){var f,g,h=a,i=[],j=c,m=c&&c[0]&&k.isXML(c[0]);while(a&&c.length){for(var n in l.filter)if((f=l.leftMatch[n].exec(a))!=null&&f[2]){var o,p,q=l.filter[n],r=f[1];g=!1,f.splice(1,1);if(r.substr(r.length-1)==="\\")continue;j===i&&(i=[]);if(l.preFilter[n]){f=l.preFilter[n](f,j,d,i,e,m);if(!f)g=o=!0;else if(f===!0)continue}if(f)for(var s=0;(p=j[s])!=null;s++)if(p){o=q(p,f,s,j);var t=e^!!o;d&&o!=null?t?g=!0:j[s]=!1:t&&(i.push(p),g=!0)}if(o!==b){d||(j=i),a=a.replace(l.match[n],"");if(!g)return[];break}}if(a===h)if(g==null)k.error(a);else break;h=a}return j},k.error=function(a){throw"Syntax error, unrecognized expression: "+a};var l=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!j.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&k.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&k.filter(b,a,!0)}},"":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("parentNode",b,f,a,e,c)},"~":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("previousSibling",b,f,a,e,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(i,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}k.error(e)},CHILD:function(a,b){var c=b[1],d=a;switch(c){case"only":case"first":while(d=d.previousSibling)if(d.nodeType===1)return!1;if(c==="first")return!0;d=a;case"last":while(d=d.nextSibling)if(d.nodeType===1)return!1;return!0;case"nth":var e=b[2],f=b[3];if(e===1&&f===0)return!0;var g=b[0],h=a.parentNode;if(h&&(h.sizcache!==g||!a.nodeIndex)){var i=0;for(d=h.firstChild;d;d=d.nextSibling)d.nodeType===1&&(d.nodeIndex=++i);h.sizcache=g}var j=a.nodeIndex-f;return e===0?j===0:j%e===0&&j/e>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c<f;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var r,s;c.documentElement.compareDocumentPosition?r=function(a,b){if(a===b){g=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(r=function(a,b){if(a===b){g=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],h=a.parentNode,i=b.parentNode,j=h;if(h===i)return s(a,b);if(!h)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return s(e[k],f[k]);return k===c?s(a,f[k],-1):s(e[k],b,1)},s=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),k.getText=function(a){var b="",c;for(var d=0;a[d];d++)c=a[d],c.nodeType===3||c.nodeType===4?b+=c.nodeValue:c.nodeType!==8&&(b+=k.getText(c.childNodes));return b},function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g<h;g++)k(a,f[g],d);return k.filter(e,d)};f.find=k,f.expr=k.selectors,f.expr[":"]=f.expr.filters,f.unique=k.uniqueSort,f.text=k.getText,f.isXMLDoc=k.isXML,f.contains=k.contains}();var O=/Until$/,P=/^(?:parents|prevUntil|prevAll)/,Q=/,/,R=/^.[^:#\[\.,]*$/,S=Array.prototype.slice,T=f.expr.match.POS,U={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(W(this,a,!1),"not",a)},filter:function(a){return this.pushStack(W(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d<e;d++)i=a[d],j[i]||(j[i]=T.test(i)?f(i,b||this.context):i);while(g&&g.ownerDocument&&g!==b){for(i in j)h=j[i],(h.jquery?h.index(g)>-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=T.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(l?l.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a=="string")return f.inArray(this[0],a?f(a):this.parent().children());return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(V(c[0])||V(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=S.call(arguments);O.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!U[a]?f.unique(e):e,(this.length>1||Q.test(d))&&P.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var X=/ jQuery\d+="(?:\d+|null)"/g,Y=/^\s+/,Z=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,$=/<([\w:]+)/,_=/<tbody/i,ba=/<|&#?\w+;/,bb=/<(?:script|object|embed|option|style)/i,bc=/checked\s*(?:[^=]|=\s*.checked.)/i,bd=/\/(java|ecma)script/i,be=/^\s*<!(?:\[CDATA\[|\-\-)/,bf={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};bf.optgroup=bf.option,bf.tbody=bf.tfoot=bf.colgroup=bf.caption=bf.thead,bf.th=bf.td,f.support.htmlSerialize||(bf._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(X,""):null;if(typeof a=="string"&&!bb.test(a)&&(f.support.leadingWhitespace||!Y.test(a))&&!bf[($.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Z,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bc.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bg(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bm)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i;b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof a[0]=="string"&&a[0].length<512&&i===c&&a[0].charAt(0)==="<"&&!bb.test(a[0])&&(f.support.checkClone||!bc.test(a[0]))&&(g=!0,h=f.fragments[a[0]],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[a[0]]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j | |
18 | -)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bi(a,d),e=bj(a),g=bj(d);for(h=0;e[h];++h)bi(e[h],g[h])}if(b){bh(a,d);if(c){e=bj(a),g=bj(d);for(h=0;e[h];++h)bh(e[h],g[h])}}e=g=null;return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!ba.test(k))k=b.createTextNode(k);else{k=k.replace(Z,"<$1></$2>");var l=($.exec(k)||["",""])[1].toLowerCase(),m=bf[l]||bf._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=_.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&Y.test(k)&&o.insertBefore(b.createTextNode(Y.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bl(k[i]);else bl(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||bd.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.expando,g=f.event.special,h=f.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&f.noData[j.nodeName.toLowerCase()])continue;c=j[f.expando];if(c){b=d[c]&&d[c][e];if(b&&b.events){for(var k in b.events)g[k]?f.event.remove(j,k):f.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[f.expando]:j.removeAttribute&&j.removeAttribute(f.expando),delete d[c]}}}});var bn=/alpha\([^)]*\)/i,bo=/opacity=([^)]*)/,bp=/([A-Z]|^ms)/g,bq=/^-?\d+(?:px)?$/i,br=/^-?\d/,bs=/^[+\-]=/,bt=/[^+\-\.\de]+/g,bu={position:"absolute",visibility:"hidden",display:"block"},bv=["Left","Right"],bw=["Top","Bottom"],bx,by,bz;f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bx(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d;if(h==="number"&&isNaN(d)||d==null)return;h==="string"&&bs.test(d)&&(d=+d.replace(bt,"")+parseFloat(f.css(a,c)),h="number"),h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bx)return bx(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){if(a.offsetWidth!==0)return bA(a,b,d);f.swap(a,bu,function(){e=bA(a,b,d)});return e}},set:function(a,b){if(!bq.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bo.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bn.test(g)?g.replace(bn,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bx(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(by=function(a,c){var d,e,g;c=c.replace(bp,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bz=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bq.test(d)&&br.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bx=by||bz,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bB=/%20/g,bC=/\[\]$/,bD=/\r?\n/g,bE=/#.*$/,bF=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bG=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bH=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,bI=/^(?:GET|HEAD)$/,bJ=/^\/\//,bK=/\?/,bL=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bM=/^(?:select|textarea)/i,bN=/\s+/,bO=/([?&])_=[^&]*/,bP=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bQ=f.fn.load,bR={},bS={},bT,bU;try{bT=e.href}catch(bV){bT=c.createElement("a"),bT.href="",bT=bT.href}bU=bP.exec(bT.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bQ)return bQ.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bL,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bM.test(this.nodeName)||bG.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bD,"\r\n")}}):{name:b.name,value:c.replace(bD,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?f.extend(!0,a,f.ajaxSettings,b):(b=a,a=f.extend(!0,f.ajaxSettings,b));for(var c in{context:1,url:1})c in b?a[c]=b[c]:c in f.ajaxSettings&&(a[c]=f.ajaxSettings[c]);return a},ajaxSettings:{url:bT,isLocal:bH.test(bU[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML}},ajaxPrefilter:bW(bR),ajaxTransport:bW(bS),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a?4:0;var o,r,u,w=l?bZ(d,v,l):b,x,y;if(a>=200&&a<300||a===304){if(d.ifModified){if(x=v.getResponseHeader("Last-Modified"))f.lastModified[k]=x;if(y=v.getResponseHeader("Etag"))f.etag[k]=y}if(a===304)c="notmodified",o=!0;else try{r=b$(d,w),c="success",o=!0}catch(z){c="parsererror",u=z}}else{u=c;if(!c||a)c="error",a<0&&(a=0)}v.status=a,v.statusText=c,o?h.resolveWith(e,[r,c,v]):h.rejectWith(e,[v,c,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,c]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bF.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bE,"").replace(bJ,bU[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bN),d.crossDomain==null&&(r=bP.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bU[1]&&r[2]==bU[2]&&(r[3]||(r[1]==="http:"?80:443))==(bU[3]||(bU[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bX(bR,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bI.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bK.test(d.url)?"&":"?")+d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bO,"$1_="+x);d.url=y+(y===d.url?(bK.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", */*; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bX(bS,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){status<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)bY(g,a[g],c,e);return d.join("&").replace(bB,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var b_=f.now(),ca=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+b_++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ca.test(b.url)||e&&ca.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ca,l),b.url===j&&(e&&(k=k.replace(ca,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cb=a.ActiveXObject?function(){for(var a in cd)cd[a](0,1)}:!1,cc=0,cd;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ce()||cf()}:ce,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cb&&delete cd[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cc,cb&&(cd||(cd={},f(a).unload(cb)),cd[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cg={},ch,ci,cj=/^(?:toggle|show|hide)$/,ck=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cl,cm=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cn,co=a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cr("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cs(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cr("hide",3),a,b,c);for(var d=0,e=this.length;d<e;d++)if(this[d].style){var g=f.css(this[d],"display");g!=="none"&&!f._data(this[d],"olddisplay")&&f._data(this[d],"olddisplay",g)}for(d=0;d<e;d++)this[d].style&&(this[d].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cr("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return this[e.queue===!1?"each":"queue"](function(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(f.support.inlineBlockNeedsLayout?(j=cs(this.nodeName),j==="inline"?this.style.display="inline-block":(this.style.display="inline",this.style.zoom=1)):this.style.display="inline-block"))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)k=new f.fx(this,b,i),h=a[i],cj.test(h)?k[h==="toggle"?d?"show":"hide":h]():(l=ck.exec(h),m=k.cur(),l?(n=parseFloat(l[2]),o=l[3]||(f.cssNumber[i]?"":"px"),o!=="px"&&(f.style(this,i,(n||1)+o),m=(n||1)/k.cur()*m,f.style(this,i,m+o)),l[1]&&(n=(l[1]==="-="?-1:1)*n+m),k.custom(m,n,o)):k.custom(m,h,""));return!0})},stop:function(a,b){a&&this.queue([]),this.each(function(){var a=f.timers,c=a.length;b||f._unmark(!0,this);while(c--)a[c].elem===this&&(b&&a[c](!0),a.splice(c,1))}),b||this.dequeue();return this}}),f.each({slideDown:cr("show",1),slideUp:cr("hide",1),slideToggle:cr("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default,d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue!==!1?f.dequeue(this):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,c){function h(a){return d.step(a)}var d=this,e=f.fx,g;this.startTime=cn||cp(),this.start=a,this.end=b,this.unit=c||this.unit||(f.cssNumber[this.prop]?"":"px"),this.now=this.start,this.pos=this.state=0,h.elem=this.elem,h()&&f.timers.push(h)&&!cl&&(co?(cl=!0,g=function(){cl&&(co(g),e.tick())},co(g)):cl=setInterval(e.tick,e.interval))},show:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=cn||cp(),c=!0,d=this.elem,e=this.options,g,h;if(a||b>=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b<a.length;++b)a[b]()||a.splice(b--,1);a.length||f.fx.stop()},interval:13,stop:function(){clearInterval(cl),cl=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit:a.elem[a.prop]=a.now}}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var ct=/^t(?:able|d|h)$/i,cu=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cv(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);f.offset.initialize();var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.offset.supportsFixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.offset.doesNotAddBorder&&(!f.offset.doesAddBorderForTableAndCells||!ct.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.offset.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.offset.supportsFixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={initialize:function(){var a=c.body,b=c.createElement("div"),d,e,g,h,i=parseFloat(f.css(a,"marginTop"))||0,j="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cu.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cu.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cv(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cv(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a&&a.style?parseFloat(f.css(a,d,"padding")):null},f.fn["outer"+c]=function(a){var b=this[0];return b&&b.style?parseFloat(f.css(b,d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c];return e.document.compatMode==="CSS1Compat"&&g||e.document.body["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var h=f.css(e,d),i=parseFloat(h);return f.isNaN(i)?h:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window); | |
19 | 0 | \ No newline at end of file |
app/assets/javascripts/jquery.pjax.js
... | ... | @@ -1,264 +0,0 @@ |
1 | -// jquery.pjax.js | |
2 | -// copyright chris wanstrath | |
3 | -// https://github.com/defunkt/jquery-pjax | |
4 | - | |
5 | -(function($){ | |
6 | - | |
7 | -// When called on a link, fetches the href with ajax into the | |
8 | -// container specified as the first parameter or with the data-pjax | |
9 | -// attribute on the link itself. | |
10 | -// | |
11 | -// Tries to make sure the back button and ctrl+click work the way | |
12 | -// you'd expect. | |
13 | -// | |
14 | -// Accepts a jQuery ajax options object that may include these | |
15 | -// pjax specific options: | |
16 | -// | |
17 | -// container - Where to stick the response body. Usually a String selector. | |
18 | -// $(container).html(xhr.responseBody) | |
19 | -// push - Whether to pushState the URL. Defaults to true (of course). | |
20 | -// replace - Want to use replaceState instead? That's cool. | |
21 | -// | |
22 | -// For convenience the first parameter can be either the container or | |
23 | -// the options object. | |
24 | -// | |
25 | -// Returns the jQuery object | |
26 | -$.fn.pjax = function( container, options ) { | |
27 | - if ( options ) | |
28 | - options.container = container | |
29 | - else | |
30 | - options = $.isPlainObject(container) ? container : {container:container} | |
31 | - | |
32 | - // We can't persist $objects using the history API so we must use | |
33 | - // a String selector. Bail if we got anything else. | |
34 | - if ( options.container && typeof options.container !== 'string' ) { | |
35 | - throw "pjax container must be a string selector!" | |
36 | - return false | |
37 | - } | |
38 | - | |
39 | - return this.live('click', function(event){ | |
40 | - // Middle click, cmd click, and ctrl click should open | |
41 | - // links in a new tab as normal. | |
42 | - if ( event.which > 1 || event.metaKey ) | |
43 | - return true | |
44 | - | |
45 | - var defaults = { | |
46 | - url: this.href, | |
47 | - container: $(this).attr('data-pjax'), | |
48 | - clickedElement: $(this), | |
49 | - fragment: null | |
50 | - } | |
51 | - | |
52 | - $.pjax($.extend({}, defaults, options)) | |
53 | - | |
54 | - event.preventDefault() | |
55 | - }) | |
56 | -} | |
57 | - | |
58 | - | |
59 | -// Loads a URL with ajax, puts the response body inside a container, | |
60 | -// then pushState()'s the loaded URL. | |
61 | -// | |
62 | -// Works just like $.ajax in that it accepts a jQuery ajax | |
63 | -// settings object (with keys like url, type, data, etc). | |
64 | -// | |
65 | -// Accepts these extra keys: | |
66 | -// | |
67 | -// container - Where to stick the response body. Must be a String. | |
68 | -// $(container).html(xhr.responseBody) | |
69 | -// push - Whether to pushState the URL. Defaults to true (of course). | |
70 | -// replace - Want to use replaceState instead? That's cool. | |
71 | -// | |
72 | -// Use it just like $.ajax: | |
73 | -// | |
74 | -// var xhr = $.pjax({ url: this.href, container: '#main' }) | |
75 | -// console.log( xhr.readyState ) | |
76 | -// | |
77 | -// Returns whatever $.ajax returns. | |
78 | -var pjax = $.pjax = function( options ) { | |
79 | - var $container = $(options.container), | |
80 | - success = options.success || $.noop | |
81 | - | |
82 | - // We don't want to let anyone override our success handler. | |
83 | - delete options.success | |
84 | - | |
85 | - // We can't persist $objects using the history API so we must use | |
86 | - // a String selector. Bail if we got anything else. | |
87 | - if ( typeof options.container !== 'string' ) | |
88 | - throw "pjax container must be a string selector!" | |
89 | - | |
90 | - options = $.extend(true, {}, pjax.defaults, options) | |
91 | - | |
92 | - if ( $.isFunction(options.url) ) { | |
93 | - options.url = options.url() | |
94 | - } | |
95 | - | |
96 | - options.context = $container | |
97 | - | |
98 | - options.success = function(data){ | |
99 | - if ( options.fragment ) { | |
100 | - // If they specified a fragment, look for it in the response | |
101 | - // and pull it out. | |
102 | - var $fragment = $(data).find(options.fragment) | |
103 | - if ( $fragment.length ) | |
104 | - data = $fragment.children() | |
105 | - else | |
106 | - return window.location = options.url | |
107 | - } else { | |
108 | - // If we got no data or an entire web page, go directly | |
109 | - // to the page and let normal error handling happen. | |
110 | - if ( !$.trim(data) || /<html/i.test(data) ) | |
111 | - return window.location = options.url | |
112 | - } | |
113 | - | |
114 | - // Make it happen. | |
115 | - this.html(data) | |
116 | - | |
117 | - // If there's a <title> tag in the response, use it as | |
118 | - // the page's title. | |
119 | - var oldTitle = document.title, | |
120 | - title = $.trim( this.find('title').remove().text() ) | |
121 | - if ( title ) document.title = title | |
122 | - | |
123 | - // No <title>? Fragment? Look for data-title and title attributes. | |
124 | - if ( !title && options.fragment ) { | |
125 | - title = $fragment.attr('title') || $fragment.data('title') | |
126 | - } | |
127 | - | |
128 | - var state = { | |
129 | - pjax: options.container, | |
130 | - fragment: options.fragment, | |
131 | - timeout: options.timeout | |
132 | - } | |
133 | - | |
134 | - // If there are extra params, save the complete URL in the state object | |
135 | - var query = $.param(options.data) | |
136 | - if ( query != "_pjax=true" ) | |
137 | - state.url = options.url + (/\?/.test(options.url) ? "&" : "?") + query | |
138 | - | |
139 | - if ( options.replace ) { | |
140 | - window.history.replaceState(state, document.title, options.url) | |
141 | - } else if ( options.push ) { | |
142 | - // this extra replaceState before first push ensures good back | |
143 | - // button behavior | |
144 | - if ( !pjax.active ) { | |
145 | - window.history.replaceState($.extend({}, state, {url:null}), oldTitle) | |
146 | - pjax.active = true | |
147 | - } | |
148 | - | |
149 | - window.history.pushState(state, document.title, options.url) | |
150 | - } | |
151 | - | |
152 | - // Google Analytics support | |
153 | - if ( (options.replace || options.push) && window._gaq ) | |
154 | - _gaq.push(['_trackPageview']) | |
155 | - | |
156 | - // If the URL has a hash in it, make sure the browser | |
157 | - // knows to navigate to the hash. | |
158 | - var hash = window.location.hash.toString() | |
159 | - if ( hash !== '' ) { | |
160 | - window.location.href = hash | |
161 | - } | |
162 | - | |
163 | - // Invoke their success handler if they gave us one. | |
164 | - success.apply(this, arguments) | |
165 | - } | |
166 | - | |
167 | - // Cancel the current request if we're already pjaxing | |
168 | - var xhr = pjax.xhr | |
169 | - if ( xhr && xhr.readyState < 4) { | |
170 | - xhr.onreadystatechange = $.noop | |
171 | - xhr.abort() | |
172 | - } | |
173 | - | |
174 | - pjax.options = options | |
175 | - pjax.xhr = $.ajax(options) | |
176 | - $(document).trigger('pjax', [pjax.xhr, options]) | |
177 | - | |
178 | - return pjax.xhr | |
179 | -} | |
180 | - | |
181 | - | |
182 | -pjax.defaults = { | |
183 | - timeout: 650, | |
184 | - push: true, | |
185 | - replace: false, | |
186 | - // We want the browser to maintain two separate internal caches: one for | |
187 | - // pjax'd partial page loads and one for normal page loads. Without | |
188 | - // adding this secret parameter, some browsers will often confuse the two. | |
189 | - data: { _pjax: true }, | |
190 | - type: 'GET', | |
191 | - dataType: 'html', | |
192 | - beforeSend: function(xhr){ | |
193 | - this.trigger('pjax:start', [xhr, pjax.options]) | |
194 | - // start.pjax is deprecated | |
195 | - this.trigger('start.pjax', [xhr, pjax.options]) | |
196 | - xhr.setRequestHeader('X-PJAX', 'true') | |
197 | - }, | |
198 | - error: function(xhr, textStatus, errorThrown){ | |
199 | - if ( textStatus !== 'abort' ) | |
200 | - window.location = pjax.options.url | |
201 | - }, | |
202 | - complete: function(xhr){ | |
203 | - this.trigger('pjax:end', [xhr, pjax.options]) | |
204 | - // end.pjax is deprecated | |
205 | - this.trigger('end.pjax', [xhr, pjax.options]) | |
206 | - } | |
207 | -} | |
208 | - | |
209 | - | |
210 | -// Used to detect initial (useless) popstate. | |
211 | -// If history.state exists, assume browser isn't going to fire initial popstate. | |
212 | -var popped = ('state' in window.history), initialURL = location.href | |
213 | - | |
214 | - | |
215 | -// popstate handler takes care of the back and forward buttons | |
216 | -// | |
217 | -// You probably shouldn't use pjax on pages with other pushState | |
218 | -// stuff yet. | |
219 | -$(window).bind('popstate', function(event){ | |
220 | - // Ignore inital popstate that some browsers fire on page load | |
221 | - var initialPop = !popped && location.href == initialURL | |
222 | - popped = true | |
223 | - if ( initialPop ) return | |
224 | - | |
225 | - var state = event.state | |
226 | - | |
227 | - if ( state && state.pjax ) { | |
228 | - var container = state.pjax | |
229 | - if ( $(container+'').length ) | |
230 | - $.pjax({ | |
231 | - url: state.url || location.href, | |
232 | - fragment: state.fragment, | |
233 | - container: container, | |
234 | - push: false, | |
235 | - timeout: state.timeout | |
236 | - }) | |
237 | - else | |
238 | - window.location = location.href | |
239 | - } | |
240 | -}) | |
241 | - | |
242 | - | |
243 | -// Add the state property to jQuery's event object so we can use it in | |
244 | -// $(window).bind('popstate') | |
245 | -if ( $.inArray('state', $.event.props) < 0 ) | |
246 | - $.event.props.push('state') | |
247 | - | |
248 | - | |
249 | -// Is pjax supported by this browser? | |
250 | -$.support.pjax = | |
251 | - window.history && window.history.pushState && window.history.replaceState | |
252 | - // pushState isn't reliable on iOS yet. | |
253 | - && !navigator.userAgent.match(/(iPod|iPhone|iPad|WebApps\/.+CFNetwork)/) | |
254 | - | |
255 | - | |
256 | -// Fall back to normalcy for older browsers. | |
257 | -if ( !$.support.pjax ) { | |
258 | - $.pjax = function( options ) { | |
259 | - window.location = $.isFunction(options.url) ? options.url() : options.url | |
260 | - } | |
261 | - $.fn.pjax = function() { return this } | |
262 | -} | |
263 | - | |
264 | -})(jQuery); |
app/assets/javascripts/rails.js
1 | 1 | /** |
2 | 2 | * Unobtrusive scripting adapter for jQuery |
3 | 3 | * |
4 | - * Requires jQuery 1.6.0 or later. | |
4 | + * Requires jQuery 1.6.0. Not compatible to jquery > 1.9 | |
5 | 5 | * https://github.com/rails/jquery-ujs |
6 | 6 | |
7 | 7 | * Uploading file using rails.js |
... | ... | @@ -131,11 +131,11 @@ |
131 | 131 | method = element.data('method'); |
132 | 132 | url = element.data('url'); |
133 | 133 | data = element.serialize(); |
134 | - if (element.data('params')) data = data + "&" + element.data('params'); | |
134 | + if (element.data('params')) data = data + "&" + element.data('params'); | |
135 | 135 | } else { |
136 | 136 | method = element.data('method'); |
137 | 137 | url = element.attr('href'); |
138 | - data = element.data('params') || null; | |
138 | + data = element.data('params') || null; | |
139 | 139 | } |
140 | 140 | |
141 | 141 | options = { | ... | ... |
app/assets/stylesheets/errbit.css
... | ... | @@ -304,7 +304,7 @@ form label.inline { display: inline; } |
304 | 304 | form .checkbox label { display: inline; } |
305 | 305 | form .required label { padding-right: 20px; background: transparent url(images/icons/required.png) right 50% no-repeat; } |
306 | 306 | form .field_with_errors label { color: #900; } |
307 | -form input[type=text], form input[type=password] { | |
307 | +form input[type=text], form input[type=password], form input[type=email] { | |
308 | 308 | width: 96%; padding: 0.8em; |
309 | 309 | font-size: 1em; |
310 | 310 | color: #787878; border: 1px solid #C6C6C6; |
... | ... | @@ -316,7 +316,7 @@ form textarea { |
316 | 316 | } |
317 | 317 | form textarea.short { height: 8em; } |
318 | 318 | form textarea.supershort { height: 4em; } |
319 | -form input[type=text]:focus, form input[type=password]:focus, form textarea:focus { | |
319 | +form input[type=text]:focus, form input[type=password]:focus, form input[type=email]:focus, form textarea:focus { | |
320 | 320 | box-shadow: 0px 0px 4px #69C; |
321 | 321 | -moz-box-shadow: 0px 0px 4px #69C; |
322 | 322 | -webkit-box-shadow: 0px 0px 4px #69C |
... | ... | @@ -654,7 +654,6 @@ table.errs td.message a { |
654 | 654 | overflow: hidden; |
655 | 655 | text-overflow: ellipsis; |
656 | 656 | -o-text-overflow: ellipsis; |
657 | - white-space: nowrap; | |
658 | 657 | /* ------ */ |
659 | 658 | } |
660 | 659 | table.errs td.message em { |
... | ... | @@ -714,7 +713,9 @@ table.deploys td.when { |
714 | 713 | .notice-pagination-loader { |
715 | 714 | visibility: hidden; |
716 | 715 | float: left; |
717 | - margin-right: 2em; | |
716 | + width: 16px; | |
717 | + height: 16px; | |
718 | + margin-right: 1em; | |
718 | 719 | } |
719 | 720 | .notice-pagination-loader img { |
720 | 721 | vertical-align: middle | ... | ... |
app/controllers/api/v1/notices_controller.rb
1 | 1 | class Api::V1::NoticesController < ApplicationController |
2 | 2 | respond_to :json, :xml |
3 | - | |
3 | + | |
4 | 4 | def index |
5 | 5 | query = {} |
6 | 6 | fields = %w{created_at message error_class} |
7 | - | |
7 | + | |
8 | 8 | if params.key?(:start_date) && params.key?(:end_date) |
9 | 9 | start_date = Time.parse(params[:start_date]).utc |
10 | 10 | end_date = Time.parse(params[:end_date]).utc |
11 | 11 | query = {:created_at => {"$lte" => end_date, "$gte" => start_date}} |
12 | 12 | end |
13 | - | |
14 | - results = benchmark("[api/v1/notices_controller] query time") { Mongoid.master["notices"].find(query, :fields => fields).to_a } | |
15 | - | |
13 | + | |
14 | + results = benchmark("[api/v1/notices_controller] query time") do | |
15 | + Notice.where(query).with(:consistency => :strong).only(fields).to_a | |
16 | + end | |
17 | + | |
16 | 18 | respond_to do |format| |
17 | 19 | format.html { render :json => Yajl.dump(results) } # render JSON if no extension specified on path |
18 | 20 | format.json { render :json => Yajl.dump(results) } |
19 | 21 | format.xml { render :xml => results } |
20 | 22 | end |
21 | 23 | end |
22 | - | |
24 | + | |
23 | 25 | end | ... | ... |
app/controllers/api/v1/problems_controller.rb
1 | 1 | class Api::V1::ProblemsController < ApplicationController |
2 | 2 | respond_to :json, :xml |
3 | - | |
3 | + | |
4 | 4 | def index |
5 | 5 | query = {} |
6 | 6 | fields = %w{app_id app_name environment message where first_notice_at last_notice_at resolved resolved_at notices_count} |
7 | - | |
7 | + | |
8 | 8 | if params.key?(:start_date) && params.key?(:end_date) |
9 | 9 | start_date = Time.parse(params[:start_date]).utc |
10 | 10 | end_date = Time.parse(params[:end_date]).utc |
11 | 11 | query = {:first_notice_at=>{"$lte"=>end_date}, "$or"=>[{:resolved_at=>nil}, {:resolved_at=>{"$gte"=>start_date}}]} |
12 | 12 | end |
13 | - | |
14 | - results = benchmark("[api/v1/problems_controller] query time") { Mongoid.master["problems"].find(query, :fields => fields).to_a } | |
15 | - | |
13 | + | |
14 | + results = benchmark("[api/v1/problems_controller] query time") do | |
15 | + Problem.where(query).with(:consistency => :strong).only(fields).to_a | |
16 | + end | |
17 | + | |
16 | 18 | respond_to do |format| |
17 | 19 | format.html { render :json => Yajl.dump(results) } # render JSON if no extension specified on path |
18 | 20 | format.json { render :json => Yajl.dump(results) } |
19 | 21 | format.xml { render :xml => results } |
20 | 22 | end |
21 | 23 | end |
22 | - | |
24 | + | |
23 | 25 | end | ... | ... |
... | ... | @@ -0,0 +1,41 @@ |
1 | +class Api::V1::StatsController < ApplicationController | |
2 | + respond_to :json, :xml | |
3 | + | |
4 | + # The stats API only requires an api_key for the given app. | |
5 | + skip_before_filter :authenticate_user! | |
6 | + before_filter :require_api_key_or_authenticate_user! | |
7 | + | |
8 | + def app | |
9 | + if problem = @app.problems.order_by(:last_notice_at.desc).first | |
10 | + @last_error_time = problem.last_notice_at | |
11 | + end | |
12 | + | |
13 | + stats = { | |
14 | + :name => @app.name, | |
15 | + :last_error_time => @last_error_time, | |
16 | + :unresolved_errors => @app.unresolved_count | |
17 | + } | |
18 | + | |
19 | + respond_to do |format| | |
20 | + format.html { render :json => Yajl.dump(stats) } # render JSON if no extension specified on path | |
21 | + format.json { render :json => Yajl.dump(stats) } | |
22 | + format.xml { render :xml => stats } | |
23 | + end | |
24 | + end | |
25 | + | |
26 | + | |
27 | + protected | |
28 | + | |
29 | + def require_api_key_or_authenticate_user! | |
30 | + if params[:api_key].present? | |
31 | + if @app = App.where(:api_key => params[:api_key]).first | |
32 | + return true | |
33 | + end | |
34 | + end | |
35 | + | |
36 | + authenticate_user! | |
37 | + end | |
38 | + | |
39 | +end | |
40 | + | |
41 | + | ... | ... |
app/controllers/application_controller.rb
... | ... | @@ -13,12 +13,28 @@ class ApplicationController < ActionController::Base |
13 | 13 | |
14 | 14 | rescue_from ActionController::RedirectBackError, :with => :redirect_to_root |
15 | 15 | |
16 | + class StrongParametersWithEagerAttributesStrategy < DecentExposure::StrongParametersStrategy | |
17 | + def attributes | |
18 | + super | |
19 | + @attributes ||= params[inflector.param_key] || {} | |
20 | + end | |
21 | + end | |
22 | + | |
23 | + decent_configuration do | |
24 | + strategy StrongParametersWithEagerAttributesStrategy | |
25 | + end | |
16 | 26 | |
17 | 27 | protected |
18 | 28 | |
19 | 29 | |
30 | + ## | |
31 | + # Check if the current_user is admin or not and redirect to root url if not | |
32 | + # | |
20 | 33 | def require_admin! |
21 | - redirect_to_root unless user_signed_in? && current_user.admin? | |
34 | + unless user_signed_in? && current_user.admin? | |
35 | + flash[:error] = "Sorry, you don't have permission to do that" | |
36 | + redirect_to_root | |
37 | + end | |
22 | 38 | end |
23 | 39 | |
24 | 40 | def redirect_to_root |
... | ... | @@ -30,4 +46,3 @@ protected |
30 | 46 | end |
31 | 47 | |
32 | 48 | end |
33 | - | ... | ... |
app/controllers/apps_controller.rb
1 | -class AppsController < InheritedResources::Base | |
1 | +class AppsController < ApplicationController | |
2 | + | |
3 | + include ProblemsSearcher | |
4 | + | |
2 | 5 | before_filter :require_admin!, :except => [:index, :show] |
3 | 6 | before_filter :parse_email_at_notices_or_set_default, :only => [:create, :update] |
7 | + before_filter :parse_notice_at_notices_or_set_default, :only => [:create, :update] | |
4 | 8 | respond_to :html |
5 | 9 | |
6 | - def show | |
7 | - respond_to do |format| | |
8 | - format.html do | |
9 | - @all_errs = !!params[:all_errs] | |
10 | + expose(:app_scope) { | |
11 | + (current_user.admin? ? App : current_user.apps) | |
12 | + } | |
13 | + | |
14 | + expose(:apps) { | |
15 | + app_scope.all.sort | |
16 | + } | |
17 | + | |
18 | + expose(:app, :ancestor => :app_scope) | |
19 | + | |
20 | + expose(:all_errs) { | |
21 | + !!params[:all_errs] | |
22 | + } | |
23 | + expose(:problems) { | |
24 | + if request.format == :atom | |
25 | + app.problems.unresolved.ordered | |
26 | + else | |
27 | + pr = app.problems | |
28 | + pr = pr.unresolved unless all_errs | |
29 | + pr.in_env( | |
30 | + params[:environment] | |
31 | + ).ordered_by(params_sort, params_order).page(params[:page]).per(current_user.per_page) | |
32 | + end | |
33 | + } | |
10 | 34 | |
11 | - @sort = params[:sort] | |
12 | - @order = params[:order] | |
13 | - @sort = "last_notice_at" unless %w{message app last_deploy_at last_notice_at count}.member?(@sort) | |
14 | - @order = "desc" unless %w{asc desc}.member?(@order) | |
35 | + expose(:deploys) { | |
36 | + app.deploys.order_by(:created_at.desc).limit(5) | |
37 | + } | |
15 | 38 | |
16 | - @problems = resource.problems | |
17 | - @problems = @problems.unresolved unless @all_errs | |
18 | - @problems = @problems.in_env(params[:environment]).ordered_by(@sort, @order).page(params[:page]).per(current_user.per_page) | |
39 | + def index; end | |
40 | + def show | |
41 | + app | |
42 | + end | |
19 | 43 | |
20 | - @selected_problems = params[:problems] || [] | |
21 | - @deploys = @app.deploys.order_by(:created_at.desc).limit(5) | |
22 | - end | |
23 | - format.atom do | |
24 | - @problems = resource.problems.unresolved.ordered | |
25 | - end | |
26 | - end | |
44 | + def new | |
45 | + plug_params(app) | |
27 | 46 | end |
28 | 47 | |
29 | 48 | def create |
30 | - @app = App.new(params[:app]) | |
31 | 49 | initialize_subclassed_issue_tracker |
32 | 50 | initialize_subclassed_notification_service |
33 | - create! | |
51 | + if app.save | |
52 | + redirect_to app_url(app), :flash => { :success => I18n.t('controllers.apps.flash.create.success') } | |
53 | + else | |
54 | + flash[:error] = I18n.t('controllers.apps.flash.create.error') | |
55 | + render :new | |
56 | + end | |
34 | 57 | end |
35 | 58 | |
36 | 59 | def update |
37 | - @app = resource | |
38 | 60 | initialize_subclassed_issue_tracker |
39 | 61 | initialize_subclassed_notification_service |
40 | - update! | |
62 | + if app.save | |
63 | + redirect_to app_url(app), :flash => { :success => I18n.t('controllers.apps.flash.update.success') } | |
64 | + else | |
65 | + flash[:error] = I18n.t('controllers.apps.flash.update.error') | |
66 | + render :edit | |
67 | + end | |
41 | 68 | end |
42 | 69 | |
43 | - def new | |
44 | - plug_params(build_resource) | |
45 | - new! | |
70 | + def edit | |
71 | + plug_params(app) | |
46 | 72 | end |
47 | 73 | |
48 | - def edit | |
49 | - plug_params(resource) | |
50 | - edit! | |
74 | + def destroy | |
75 | + if app.destroy | |
76 | + redirect_to apps_url, :flash => { :success => I18n.t('controllers.apps.flash.destroy.success') } | |
77 | + else | |
78 | + flash[:error] = I18n.t('controllers.apps.flash.destroy.error') | |
79 | + render :show | |
80 | + end | |
51 | 81 | end |
52 | 82 | |
53 | 83 | protected |
54 | - def collection | |
55 | - @apps ||= end_of_association_chain.all.sort | |
56 | - end | |
57 | 84 | |
58 | 85 | def initialize_subclassed_issue_tracker |
59 | 86 | # set the app's issue tracker |
60 | 87 | if params[:app][:issue_tracker_attributes] && tracker_type = params[:app][:issue_tracker_attributes][:type] |
61 | 88 | if IssueTracker.subclasses.map(&:name).concat(["IssueTracker"]).include?(tracker_type) |
62 | - @app.issue_tracker = tracker_type.constantize.new(params[:app][:issue_tracker_attributes]) | |
89 | + app.issue_tracker = tracker_type.constantize.new(params[:app][:issue_tracker_attributes]) | |
63 | 90 | end |
64 | 91 | end |
65 | 92 | end |
... | ... | @@ -68,21 +95,11 @@ class AppsController < InheritedResources::Base |
68 | 95 | # set the app's notification service |
69 | 96 | if params[:app][:notification_service_attributes] && notification_type = params[:app][:notification_service_attributes][:type] |
70 | 97 | if NotificationService.subclasses.map(&:name).concat(["NotificationService"]).include?(notification_type) |
71 | - @app.notification_service = notification_type.constantize.new(params[:app][:notification_service_attributes]) | |
98 | + app.notification_service = notification_type.constantize.new(params[:app][:notification_service_attributes]) | |
72 | 99 | end |
73 | 100 | end |
74 | 101 | end |
75 | 102 | |
76 | - def begin_of_association_chain | |
77 | - # Filter the @apps collection to apps watched by the current user, unless user is an admin. | |
78 | - # If user is an admin, then no filter is applied, and all apps are shown. | |
79 | - current_user unless current_user.admin? | |
80 | - end | |
81 | - | |
82 | - def interpolation_options | |
83 | - {:app_name => resource.name} | |
84 | - end | |
85 | - | |
86 | 103 | def plug_params app |
87 | 104 | app.watchers.build if app.watchers.none? |
88 | 105 | app.issue_tracker = IssueTracker.new unless app.issue_tracker_configured? |
... | ... | @@ -105,5 +122,20 @@ class AppsController < InheritedResources::Base |
105 | 122 | end |
106 | 123 | end |
107 | 124 | end |
125 | + | |
126 | + def parse_notice_at_notices_or_set_default | |
127 | + if params[:app][:notification_service_attributes] && val = params[:app][:notification_service_attributes][:notify_at_notices] | |
128 | + # Sanitize negative values, split on comma, | |
129 | + # strip, parse as integer, remove all '0's. | |
130 | + # If empty, set as default and show an error message. | |
131 | + notify_at_notices = val.gsub(/-\d+/,"").split(",").map{|v| v.strip.to_i } | |
132 | + if notify_at_notices.any? | |
133 | + params[:app][:notification_service_attributes][:notify_at_notices] = notify_at_notices | |
134 | + else | |
135 | + default_array = params[:app][:notification_service_attributes][:notify_at_notices] = Errbit::Config.notify_at_notices | |
136 | + flash[:error] = "Couldn't parse your notification frequency. Value was reset to default (#{default_array.join(', ')})." | |
137 | + end | |
138 | + end | |
139 | + end | |
108 | 140 | end |
109 | 141 | ... | ... |
app/controllers/notices_controller.rb
1 | 1 | class NoticesController < ApplicationController |
2 | - respond_to :xml | |
2 | + | |
3 | + class ParamsError < StandardError; end | |
3 | 4 | |
4 | 5 | skip_before_filter :authenticate_user!, :only => :create |
5 | 6 | |
7 | + rescue_from ParamsError, :with => :bad_params | |
8 | + | |
6 | 9 | def create |
7 | 10 | # params[:data] if the notice came from a GET request, raw_post if it came via POST |
8 | - notice = App.report_error!(params[:data] || request.raw_post) | |
9 | - api_xml = notice.to_xml(:only => false, :methods => [:id]) do |xml| | |
10 | - xml.url locate_url(notice.id, :host => Errbit::Config.host) | |
11 | + report = ErrorReport.new(notice_params) | |
12 | + | |
13 | + if report.valid? | |
14 | + report.generate_notice! | |
15 | + api_xml = report.notice.to_xml(:only => false, :methods => [:id]) do |xml| | |
16 | + xml.url locate_url(report.notice.id, :host => Errbit::Config.host) | |
17 | + end | |
18 | + render :xml => api_xml | |
19 | + else | |
20 | + render :text => "Your API key is unknown", :status => 422 | |
11 | 21 | end |
12 | - render :xml => api_xml | |
13 | 22 | end |
14 | 23 | |
15 | 24 | # Redirects a notice to the problem page. Useful when using User Information at Airbrake gem. |
... | ... | @@ -17,4 +26,20 @@ class NoticesController < ApplicationController |
17 | 26 | problem = Notice.find(params[:id]).problem |
18 | 27 | redirect_to app_problem_path(problem.app, problem) |
19 | 28 | end |
29 | + | |
30 | + private | |
31 | + | |
32 | + def notice_params | |
33 | + return @notice_params if @notice_params | |
34 | + @notice_params = params[:data] || request.raw_post | |
35 | + if @notice_params.blank? | |
36 | + raise ParamsError.new('Need a data params in GET or raw post data') | |
37 | + end | |
38 | + @notice_params | |
39 | + end | |
40 | + | |
41 | + def bad_params(exception) | |
42 | + render :text => exception.message, :status => :bad_request | |
43 | + end | |
44 | + | |
20 | 45 | end | ... | ... |
app/controllers/problems_controller.rb
1 | +## | |
2 | +# Manage problems | |
3 | +# | |
4 | +# List of actions available : | |
5 | +# MEMBER => :show, :edit, :update, :create, :destroy, :resolve, :unresolve, :create_issue, :unlink_issue | |
6 | +# COLLECTION => :index, :all, :destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several, :search | |
1 | 7 | class ProblemsController < ApplicationController |
2 | - before_filter :find_app, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several] | |
3 | - before_filter :find_problem, :except => [:index, :all, :destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several] | |
4 | - before_filter :find_selected_problems, :only => [:destroy_several, :resolve_several, :unresolve_several, :merge_several, :unmerge_several] | |
5 | - before_filter :set_sorting_params, :only => [:index, :all] | |
6 | - before_filter :set_tracker_params, :only => [:create_issue] | |
7 | 8 | |
8 | - def index | |
9 | - app_scope = current_user.admin? ? App.all : current_user.apps | |
10 | 9 | |
11 | - @problems = Problem.for_apps(app_scope).in_env(params[:environment]).unresolved.ordered_by(@sort, @order) | |
12 | - @selected_problems = params[:problems] || [] | |
13 | - respond_to do |format| | |
14 | - format.html do | |
15 | - @problems = @problems.page(params[:page]).per(current_user.per_page) | |
16 | - end | |
17 | - format.atom | |
10 | + include ProblemsSearcher | |
11 | + | |
12 | + before_filter :need_selected_problem, :only => [ | |
13 | + :resolve_several, :unresolve_several, :unmerge_several | |
14 | + ] | |
15 | + | |
16 | + expose(:app) { | |
17 | + if current_user.admin? | |
18 | + App.find(params[:app_id]) | |
19 | + else | |
20 | + current_user.apps.find(params[:app_id]) | |
18 | 21 | end |
19 | - end | |
22 | + } | |
20 | 23 | |
21 | - def all | |
22 | - app_scope = current_user.admin? ? App.all : current_user.apps | |
23 | - @problems = Problem.for_apps(app_scope).ordered_by(@sort, @order).page(params[:page]).per(current_user.per_page) | |
24 | - @selected_problems = params[:problems] || [] | |
25 | - end | |
24 | + expose(:problem) { | |
25 | + app.problems.find(params[:id]) | |
26 | + } | |
27 | + | |
28 | + | |
29 | + expose(:all_errs) { | |
30 | + params[:all_errs] | |
31 | + } | |
32 | + | |
33 | + expose(:app_scope) { | |
34 | + apps = current_user.admin? ? App.all : current_user.apps | |
35 | + params[:app_id] ? apps.where(:_id => params[:app_id]) : apps | |
36 | + } | |
37 | + | |
38 | + expose(:params_environement) { | |
39 | + params[:environment] | |
40 | + } | |
41 | + | |
42 | + expose(:problems) { | |
43 | + pro = Problem.for_apps( | |
44 | + app_scope | |
45 | + ).in_env( | |
46 | + params_environement | |
47 | + ).all_else_unresolved(all_errs).ordered_by(params_sort, params_order) | |
48 | + | |
49 | + if request.format == :html | |
50 | + pro.page(params[:page]).per(current_user.per_page) | |
51 | + else | |
52 | + pro | |
53 | + end | |
54 | + } | |
55 | + | |
56 | + def index; end | |
26 | 57 | |
27 | 58 | def show |
28 | - @notices = @problem.notices.reverse_ordered.page(params[:notice]).per(1) | |
59 | + @notices = problem.notices.reverse_ordered.page(params[:notice]).per(1) | |
29 | 60 | @notice = @notices.first |
30 | 61 | @comment = Comment.new |
31 | - if request.headers['X-PJAX'] | |
32 | - params["_pjax"] = nil | |
33 | - render :layout => false | |
34 | - end | |
35 | 62 | end |
36 | 63 | |
37 | 64 | def create_issue |
38 | - issue_creation = IssueCreation.new(@problem, current_user, params[:tracker]) | |
65 | + IssueTracker.update_url_options(request) | |
66 | + issue_creation = IssueCreation.new(problem, current_user, params[:tracker]) | |
39 | 67 | |
40 | 68 | unless issue_creation.execute |
41 | - flash[:error] = issue_creation.errors[:base].first | |
69 | + flash[:error] = issue_creation.errors.full_messages.join(', ') | |
42 | 70 | end |
43 | 71 | |
44 | - redirect_to app_problem_path(@app, @problem) | |
72 | + redirect_to app_problem_path(app, problem) | |
45 | 73 | end |
46 | 74 | |
47 | 75 | def unlink_issue |
48 | - @problem.update_attribute :issue_link, nil | |
49 | - redirect_to app_problem_path(@app, @problem) | |
76 | + problem.update_attribute :issue_link, nil | |
77 | + redirect_to app_problem_path(app, problem) | |
50 | 78 | end |
51 | 79 | |
52 | 80 | def resolve |
53 | - @problem.resolve! | |
81 | + problem.resolve! | |
54 | 82 | flash[:success] = 'Great news everyone! The err has been resolved.' |
55 | 83 | redirect_to :back |
56 | 84 | rescue ActionController::RedirectBackError |
57 | - redirect_to app_path(@app) | |
85 | + redirect_to app_path(app) | |
58 | 86 | end |
59 | 87 | |
60 | 88 | def resolve_several |
61 | - @selected_problems.each(&:resolve!) | |
62 | - flash[:success] = "Great news everyone! #{I18n.t(:n_errs_have, :count => @selected_problems.count)} been resolved." | |
89 | + selected_problems.each(&:resolve!) | |
90 | + flash[:success] = "Great news everyone! #{I18n.t(:n_errs_have, :count => selected_problems.count)} been resolved." | |
63 | 91 | redirect_to :back |
64 | 92 | end |
65 | 93 | |
66 | 94 | def unresolve_several |
67 | - @selected_problems.each(&:unresolve!) | |
68 | - flash[:success] = "#{I18n.t(:n_errs_have, :count => @selected_problems.count)} been unresolved." | |
95 | + selected_problems.each(&:unresolve!) | |
96 | + flash[:success] = "#{I18n.t(:n_errs_have, :count => selected_problems.count)} been unresolved." | |
69 | 97 | redirect_to :back |
70 | 98 | end |
71 | 99 | |
100 | + ## | |
101 | + # Action to merge several Problem in One problem | |
102 | + # | |
103 | + # @param [ Array<String> ] :problems the list of problem ids | |
104 | + # | |
72 | 105 | def merge_several |
73 | - if @selected_problems.length < 2 | |
74 | - flash[:notice] = "You must select at least two errors to merge" | |
106 | + if selected_problems.length < 2 | |
107 | + flash[:notice] = I18n.t('controllers.problems.flash.need_two_errors_merge') | |
75 | 108 | else |
76 | - @merged_problem = Problem.merge!(@selected_problems) | |
77 | - flash[:notice] = "#{@selected_problems.count} errors have been merged." | |
109 | + ProblemMerge.new(selected_problems).merge | |
110 | + flash[:notice] = I18n.t('controllers.problems.flash.merge_several.success', :nb => selected_problems.count) | |
78 | 111 | end |
79 | 112 | redirect_to :back |
80 | 113 | end |
81 | 114 | |
82 | 115 | def unmerge_several |
83 | - all = @selected_problems.map(&:unmerge!).flatten | |
116 | + all = selected_problems.map(&:unmerge!).flatten | |
84 | 117 | flash[:success] = "#{I18n.t(:n_errs_have, :count => all.length)} been unmerged." |
85 | 118 | redirect_to :back |
86 | 119 | end |
87 | 120 | |
88 | 121 | def destroy_several |
89 | - nb_problem_destroy = ProblemDestroy.execute(@selected_problems) | |
122 | + nb_problem_destroy = ProblemDestroy.execute(selected_problems) | |
90 | 123 | flash[:notice] = "#{I18n.t(:n_errs_have, :count => nb_problem_destroy)} been deleted." |
91 | 124 | redirect_to :back |
92 | 125 | end |
93 | 126 | |
94 | - protected | |
95 | - def find_app | |
96 | - @app = App.find(params[:app_id]) | |
97 | - | |
98 | - # Mongoid Bug: could not chain: current_user.apps.find_by_id! | |
99 | - # apparently finding by 'watchers.email' and 'id' is broken | |
100 | - raise(Mongoid::Errors::DocumentNotFound.new(App,@app.id)) unless current_user.admin? || current_user.watching?(@app) | |
101 | - end | |
102 | - | |
103 | - def find_problem | |
104 | - @problem = @app.problems.find(params[:id]) | |
105 | - end | |
106 | - | |
107 | - def set_tracker_params | |
108 | - IssueTracker.default_url_options[:host] = request.host | |
109 | - IssueTracker.default_url_options[:port] = request.port | |
110 | - IssueTracker.default_url_options[:protocol] = request.scheme | |
127 | + def search | |
128 | + ps = Problem.search(params[:search]).for_apps(app_scope).in_env(params[:environment]).all_else_unresolved(params[:all_errs]).ordered_by(params_sort, params_order) | |
129 | + selected_problems = params[:problems] || [] | |
130 | + self.problems = ps.page(params[:page]).per(2) | |
131 | + respond_to do |format| | |
132 | + format.html { render :index } | |
133 | + format.js | |
111 | 134 | end |
135 | + end | |
112 | 136 | |
113 | - def find_selected_problems | |
114 | - err_ids = (params[:problems] || []).compact | |
115 | - if err_ids.empty? | |
116 | - flash[:notice] = "You have not selected any errors" | |
117 | - redirect_to :back | |
118 | - else | |
119 | - @selected_problems = Array(Problem.find(err_ids)) | |
120 | - end | |
121 | - end | |
137 | + protected | |
122 | 138 | |
123 | - def set_sorting_params | |
124 | - @sort = params[:sort] | |
125 | - @sort = "last_notice_at" unless %w{app message last_notice_at last_deploy_at count}.member?(@sort) | |
126 | - @order = params[:order] || "desc" | |
139 | + ## | |
140 | + # Redirect :back if no errors selected | |
141 | + # | |
142 | + def need_selected_problem | |
143 | + if err_ids.empty? | |
144 | + flash[:notice] = I18n.t('controllers.problems.flash.no_select_problem') | |
145 | + redirect_to :back | |
127 | 146 | end |
147 | + end | |
128 | 148 | end |
129 | 149 | ... | ... |
... | ... | @@ -0,0 +1,34 @@ |
1 | +# Include to do a Search | |
2 | +# TODO: Need to be in a Dedicated Object ProblemsSearch with params like input | |
3 | +# | |
4 | +module ProblemsSearcher | |
5 | + extend ActiveSupport::Concern | |
6 | + | |
7 | + included do | |
8 | + | |
9 | + expose(:params_sort) { | |
10 | + unless %w{app message last_notice_at last_deploy_at count}.member?(params[:sort]) | |
11 | + "last_notice_at" | |
12 | + else | |
13 | + params[:sort] | |
14 | + end | |
15 | + } | |
16 | + | |
17 | + expose(:params_order){ | |
18 | + unless %w{asc desc}.member?(params[:order]) | |
19 | + 'desc' | |
20 | + else | |
21 | + params[:order] | |
22 | + end | |
23 | + } | |
24 | + | |
25 | + expose(:selected_problems) { | |
26 | + Array(Problem.find(err_ids)) | |
27 | + } | |
28 | + | |
29 | + expose(:err_ids) { | |
30 | + (params[:problems] || []).compact | |
31 | + } | |
32 | + | |
33 | + end | |
34 | +end | ... | ... |
app/controllers/users_controller.rb
... | ... | @@ -2,71 +2,76 @@ class UsersController < ApplicationController |
2 | 2 | respond_to :html |
3 | 3 | |
4 | 4 | before_filter :require_admin!, :except => [:edit, :update] |
5 | - before_filter :find_user, :only => [:show, :edit, :update, :destroy, :unlink_github] | |
6 | 5 | before_filter :require_user_edit_priviledges, :only => [:edit, :update] |
7 | 6 | |
8 | - def index | |
9 | - @users = User.all.page(params[:page]).per(current_user.per_page) | |
10 | - end | |
7 | + expose(:user, :attributes => :user_params) | |
8 | + expose(:users) { | |
9 | + User.all.page(params[:page]).per(current_user.per_page) | |
10 | + } | |
11 | 11 | |
12 | - def new | |
13 | - @user = User.new | |
14 | - end | |
12 | + def index; end | |
13 | + def new; end | |
14 | + def show; end | |
15 | 15 | |
16 | 16 | def create |
17 | - @user = User.new(params[:user]) | |
18 | - | |
19 | - # Set protected attributes | |
20 | - @user.admin = params[:user].try(:[], :admin) if current_user.admin? | |
21 | - | |
22 | - if @user.save | |
23 | - flash[:success] = "#{@user.name} is now part of the team. Be sure to add them as a project watcher." | |
24 | - redirect_to user_path(@user) | |
17 | + if user.save | |
18 | + flash[:success] = "#{user.name} is now part of the team. Be sure to add them as a project watcher." | |
19 | + redirect_to user_path(user) | |
25 | 20 | else |
26 | 21 | render :new |
27 | 22 | end |
28 | 23 | end |
29 | 24 | |
30 | 25 | def update |
31 | - # Devise Hack | |
32 | - if params[:user][:password].blank? && params[:user][:password_confirmation].blank? | |
33 | - params[:user].delete(:password) | |
34 | - params[:user].delete(:password_confirmation) | |
35 | - end | |
36 | - | |
37 | - # Set protected attributes | |
38 | - @user.admin = params[:user][:admin] if current_user.admin? | |
39 | - | |
40 | - if @user.update_attributes(params[:user]) | |
41 | - flash[:success] = "#{@user.name}'s information was successfully updated" | |
42 | - redirect_to user_path(@user) | |
26 | + if user.update_attributes(user_params) | |
27 | + flash[:success] = I18n.t('controllers.users.flash.update.success', :name => user.name) | |
28 | + redirect_to user_path(user) | |
43 | 29 | else |
44 | 30 | render :edit |
45 | 31 | end |
46 | 32 | end |
47 | 33 | |
34 | + ## | |
35 | + # Destroy the user pass in args | |
36 | + # | |
37 | + # @param [ String ] id the id of user we want delete | |
38 | + # | |
48 | 39 | def destroy |
49 | - @user.destroy | |
50 | - | |
51 | - flash[:success] = "That's sad. #{@user.name} is no longer part of your team." | |
40 | + if user == current_user | |
41 | + flash[:error] = I18n.t('controllers.users.flash.destroy.error') | |
42 | + else | |
43 | + UserDestroy.new(user).destroy | |
44 | + flash[:success] = I18n.t('controllers.users.flash.destroy.success', :name => user.name) | |
45 | + end | |
52 | 46 | redirect_to users_path |
53 | 47 | end |
54 | 48 | |
55 | 49 | def unlink_github |
56 | - @user.update_attributes :github_login => nil, :github_oauth_token => nil | |
57 | - redirect_to user_path(@user) | |
50 | + user.update_attributes :github_login => nil, :github_oauth_token => nil | |
51 | + redirect_to user_path(user) | |
58 | 52 | end |
59 | 53 | |
60 | 54 | protected |
61 | 55 | |
62 | - def find_user | |
63 | - @user = User.find(params[:id]) | |
64 | - end | |
65 | - | |
66 | 56 | def require_user_edit_priviledges |
67 | - can_edit = current_user == @user || current_user.admin? | |
57 | + can_edit = current_user == user || current_user.admin? | |
68 | 58 | redirect_to(root_path) and return(false) unless can_edit |
69 | 59 | end |
70 | 60 | |
61 | + def user_params | |
62 | + @user_params ||= params[:user] ? params.require(:user).permit(*user_permit_params) : {} | |
63 | + end | |
64 | + | |
65 | + def user_permit_params | |
66 | + @user_permit_params ||= [:name,:username, :email, :github_login, :per_page, :time_zone] | |
67 | + @user_permit_params << :admin if current_user.admin? && current_user.id != params[:id] | |
68 | + @user_permit_params |= [:password, :password_confirmation] if user_password_params.values.all?{|pa| !pa.blank? } | |
69 | + @user_permit_params | |
70 | + end | |
71 | + | |
72 | + def user_password_params | |
73 | + @user_password_params ||= params[:user] ? params.require(:user).permit(:password, :password_confirmation) : {} | |
74 | + end | |
75 | + | |
71 | 76 | end |
72 | 77 | ... | ... |
app/helpers/application_helper.rb
... | ... | @@ -8,11 +8,11 @@ module ApplicationHelper |
8 | 8 | notices.each_with_index do |notice,idx| |
9 | 9 | cal.event do |event| |
10 | 10 | event.summary = "#{idx+1} #{notice.message.to_s}" |
11 | - event.description = notice.request['url'] | |
11 | + event.description = notice.url if notice.url | |
12 | 12 | event.dtstart = notice.created_at.utc |
13 | 13 | event.dtend = notice.created_at.utc + 60.minutes |
14 | 14 | event.organizer = notice.server_environment && notice.server_environment["hostname"] |
15 | - event.location = notice.server_environment && notice.server_environment["project-root"] | |
15 | + event.location = notice.project_root | |
16 | 16 | event.url = app_problem_url(:app_id => notice.problem.app.id, :id => notice.problem) |
17 | 17 | end |
18 | 18 | end |
... | ... | @@ -57,7 +57,7 @@ module ApplicationHelper |
57 | 57 | total = (options[:total] || total_from_tallies(tallies)) |
58 | 58 | percent = 100.0 / total.to_f |
59 | 59 | rows = tallies.map {|value, count| [(count.to_f * percent), value]} \ |
60 | - .sort {|a, b| a[0] <=> b[0]} | |
60 | + .sort {|a, b| b[0] <=> a[0]} | |
61 | 61 | render "problems/tally_table", :rows => rows |
62 | 62 | end |
63 | 63 | ... | ... |
app/helpers/apps_helper.rb
... | ... | @@ -4,7 +4,7 @@ module AppsHelper |
4 | 4 | html = link_to('copy settings from another app', '#', |
5 | 5 | :class => 'button copy_config') |
6 | 6 | html << select("duplicate", "app", |
7 | - App.all.reject{|a| a == @app }. | |
7 | + App.all.asc(:name).reject{|a| a == @app }. | |
8 | 8 | collect{|p| [ p.name, p.id ] }, {:include_blank => "[choose app]"}, |
9 | 9 | {:class => "choose_other_app", :style => "display: none;"}) |
10 | 10 | return html |
... | ... | @@ -41,7 +41,7 @@ module AppsHelper |
41 | 41 | def detect_any_apps_with_attributes |
42 | 42 | @any_github_repos = @any_issue_trackers = @any_deploys = @any_bitbucket_repos = @any_notification_services = false |
43 | 43 | |
44 | - @apps.each do |app| | |
44 | + apps.each do |app| | |
45 | 45 | @any_github_repos ||= app.github_repo? |
46 | 46 | @any_bitbucket_repos ||= app.bitbucket_repo? |
47 | 47 | @any_issue_trackers ||= app.issue_tracker_configured? | ... | ... |
app/helpers/backtrace_line_helper.rb
1 | 1 | module BacktraceLineHelper |
2 | 2 | def link_to_source_file(line, &block) |
3 | 3 | text = capture_haml(&block) |
4 | - line.in_app? ? link_to_in_app_source_file(line, text) : link_to_external_source_file(text) | |
4 | + link_to_in_app_source_file(line, text) || link_to_external_source_file(text) | |
5 | 5 | end |
6 | 6 | |
7 | 7 | private |
8 | 8 | def link_to_in_app_source_file(line, text) |
9 | - link_to_repo_source_file(line, text) || link_to_issue_tracker_file(line, text) | |
9 | + return unless line.in_app? | |
10 | + if line.file_name =~ /\.js$/ | |
11 | + link_to_hosted_javascript(line, text) | |
12 | + else | |
13 | + link_to_repo_source_file(line, text) || | |
14 | + link_to_issue_tracker_file(line, text) | |
15 | + end | |
10 | 16 | end |
11 | 17 | |
12 | 18 | def link_to_repo_source_file(line, text) |
13 | 19 | link_to_github(line, text) || link_to_bitbucket(line, text) |
14 | 20 | end |
15 | 21 | |
22 | + def link_to_hosted_javascript(line, text) | |
23 | + if line.app.asset_host? | |
24 | + link_to(text, "#{line.app.asset_host}/#{line.file_relative}", :target => '_blank') | |
25 | + end | |
26 | + end | |
27 | + | |
16 | 28 | def link_to_external_source_file(text) |
17 | 29 | text |
18 | 30 | end |
... | ... | @@ -31,7 +43,7 @@ module BacktraceLineHelper |
31 | 43 | |
32 | 44 | def link_to_issue_tracker_file(line, text = nil) |
33 | 45 | return unless line.app.issue_tracker && line.app.issue_tracker.respond_to?(:url_to_file) |
34 | - href = line.app.issue_tracker.url_to_file(line.file, line.number) | |
46 | + href = line.app.issue_tracker.url_to_file(line.file_relative, line.number) | |
35 | 47 | link_to(text || line.file_name, href, :target => '_blank') |
36 | 48 | end |
37 | 49 | ... | ... |
app/helpers/problems_helper.rb
... | ... | @@ -11,17 +11,22 @@ module ProblemsHelper |
11 | 11 | end |
12 | 12 | |
13 | 13 | def gravatar_tag(email, options = {}) |
14 | + return nil unless email.present? | |
15 | + | |
14 | 16 | image_tag gravatar_url(email, options), :alt => email, :class => 'gravatar' |
15 | 17 | end |
16 | 18 | |
17 | 19 | def gravatar_url(email, options = {}) |
20 | + return nil unless email.present? | |
21 | + | |
18 | 22 | default_options = { |
19 | 23 | :d => Errbit::Config.gravatar_default, |
20 | 24 | } |
21 | 25 | options.reverse_merge! default_options |
22 | 26 | params = options.extract!(:s, :d).delete_if { |k, v| v.blank? } |
23 | 27 | email_hash = Digest::MD5.hexdigest(email) |
24 | - "http://www.gravatar.com/avatar/#{email_hash}?#{params.to_query}" | |
28 | + url = request.ssl? ? "https://secure.gravatar.com" : "http://www.gravatar.com" | |
29 | + "#{url}/avatar/#{email_hash}?#{params.to_query}" | |
25 | 30 | end |
26 | 31 | end |
27 | 32 | ... | ... |
app/helpers/sort_helper.rb
1 | 1 | # encoding: utf-8 |
2 | 2 | module SortHelper |
3 | - | |
3 | + | |
4 | 4 | def link_for_sort(name, field=nil) |
5 | 5 | field ||= name.underscore |
6 | - current = (@sort == field) | |
7 | - order = (current && (@order == "asc")) ? "desc" : "asc" | |
6 | + current = (params_sort == field) | |
7 | + order = (current && (params_order == "asc")) ? "desc" : "asc" | |
8 | 8 | url = request.path + "?sort=#{field}&order=#{order}" |
9 | 9 | options = {} |
10 | 10 | options.merge!(:class => "current #{order}") if current |
11 | 11 | link_to(name, url, options) |
12 | 12 | end |
13 | - | |
13 | + | |
14 | 14 | end | ... | ... |
app/interactors/issue_creation.rb
... | ... | @@ -41,15 +41,11 @@ class IssueCreation |
41 | 41 | end |
42 | 42 | |
43 | 43 | def execute |
44 | - if tracker | |
45 | - begin | |
46 | - tracker.create_issue problem, user | |
47 | - rescue => ex | |
48 | - Rails.logger.error "Error during issue creation: " << ex.message | |
49 | - errors.add :base, "There was an error during issue creation: #{ex.message}" | |
50 | - end | |
51 | - end | |
52 | - | |
44 | + tracker.create_issue problem, user if tracker | |
53 | 45 | errors.empty? |
46 | + rescue => ex | |
47 | + Rails.logger.error "Error during issue creation: " << ex.message | |
48 | + errors.add :base, "There was an error during issue creation: #{ex.message}" | |
49 | + false | |
54 | 50 | end |
55 | 51 | end | ... | ... |
app/interactors/problem_destroy.rb
... | ... | @@ -37,12 +37,12 @@ class ProblemDestroy |
37 | 37 | end |
38 | 38 | |
39 | 39 | def delete_errs |
40 | - Err.collection.remove(:_id => { '$in' => errs_id }) | |
41 | - Notice.collection.remove(:err_id => { '$in' => errs_id }) | |
40 | + Notice.delete_all(:err_id => { '$in' => errs_id }) | |
41 | + Err.delete_all(:_id => { '$in' => errs_id }) | |
42 | 42 | end |
43 | 43 | |
44 | 44 | def delete_comments |
45 | - Comment.collection.remove(:_id => { '$in' => comments_id }) | |
45 | + Comment.delete_all(:_id => { '$in' => comments_id }) | |
46 | 46 | end |
47 | 47 | |
48 | 48 | end | ... | ... |
... | ... | @@ -0,0 +1,28 @@ |
1 | +require 'problem_destroy' | |
2 | + | |
3 | +class ProblemMerge | |
4 | + def initialize(*problems) | |
5 | + problems = problems.flatten.uniq | |
6 | + @merged_problem = problems[0] | |
7 | + @child_problems = problems[1..-1] | |
8 | + raise ArgumentError.new("need almost 2 uniq different problems") if @child_problems.empty? | |
9 | + end | |
10 | + attr_reader :merged_problem, :child_problems | |
11 | + | |
12 | + def merge | |
13 | + child_problems.each do |problem| | |
14 | + merged_problem.errs.concat problem.errs | |
15 | + merged_problem.comments.concat problem.comments | |
16 | + problem.reload # deference all associate objet to avoid delete him after | |
17 | + ProblemDestroy.execute(problem) | |
18 | + end | |
19 | + reset_cached_attributes | |
20 | + merged_problem | |
21 | + end | |
22 | + | |
23 | + private | |
24 | + | |
25 | + def reset_cached_attributes | |
26 | + ProblemUpdaterCache.new(merged_problem).update | |
27 | + end | |
28 | +end | ... | ... |
... | ... | @@ -0,0 +1,88 @@ |
1 | +class ProblemUpdaterCache | |
2 | + def initialize(problem, notice=nil) | |
3 | + @problem = problem | |
4 | + @notice = notice | |
5 | + end | |
6 | + attr_reader :problem | |
7 | + | |
8 | + ## | |
9 | + # Update cache information about child associate to this problem | |
10 | + # | |
11 | + # update the notices count, and some notice informations | |
12 | + # | |
13 | + # @return [ Problem ] the problem with this update | |
14 | + # | |
15 | + def update | |
16 | + update_notices_count | |
17 | + update_notices_cache | |
18 | + problem | |
19 | + end | |
20 | + | |
21 | + private | |
22 | + | |
23 | + def update_notices_count | |
24 | + if @notice | |
25 | + problem.inc(:notices_count, 1) | |
26 | + else | |
27 | + problem.update_attribute( | |
28 | + :notices_count, problem.notices.count | |
29 | + ) | |
30 | + end | |
31 | + end | |
32 | + | |
33 | + ## | |
34 | + # Update problem statistique from some notice information | |
35 | + # | |
36 | + def update_notices_cache | |
37 | + first_notice = notices.first | |
38 | + last_notice = notices.last | |
39 | + notice ||= @notice || first_notice | |
40 | + | |
41 | + attrs = {} | |
42 | + attrs[:first_notice_at] = first_notice.created_at if first_notice | |
43 | + attrs[:last_notice_at] = last_notice.created_at if last_notice | |
44 | + attrs.merge!( | |
45 | + :message => notice.message, | |
46 | + :where => notice.where, | |
47 | + :messages => attribute_count(:message, messages), | |
48 | + :hosts => attribute_count(:host, hosts), | |
49 | + :user_agents => attribute_count(:user_agent_string, user_agents) | |
50 | + ) if notice | |
51 | + problem.update_attributes!(attrs) | |
52 | + end | |
53 | + | |
54 | + def notices | |
55 | + @notices ||= @notice ? [@notice].sort(&:created_at) : problem.notices.order_by([:created_at, :asc]) | |
56 | + end | |
57 | + | |
58 | + def messages | |
59 | + @notice ? problem.messages : {} | |
60 | + end | |
61 | + | |
62 | + def hosts | |
63 | + @notice ? problem.hosts : {} | |
64 | + end | |
65 | + | |
66 | + def user_agents | |
67 | + @notice ? problem.user_agents : {} | |
68 | + end | |
69 | + | |
70 | + private | |
71 | + | |
72 | + def attribute_count(value, init) | |
73 | + init.tap do |counts| | |
74 | + notices.each do |notice| | |
75 | + counts[attribute_index(notice.send(value))] ||= { | |
76 | + 'value' => notice.send(value), | |
77 | + 'count' => 0 | |
78 | + } | |
79 | + counts[attribute_index(notice.send(value))]['count'] += 1 | |
80 | + end | |
81 | + end | |
82 | + end | |
83 | + | |
84 | + def attribute_index(value) | |
85 | + @attributes_index ||= {} | |
86 | + @attributes_index[value.to_s] ||= Digest::MD5.hexdigest(value.to_s) | |
87 | + end | |
88 | +end | ... | ... |
... | ... | @@ -0,0 +1,32 @@ |
1 | +require 'problem_destroy' | |
2 | + | |
3 | +class ResolvedProblemClearer | |
4 | + | |
5 | + ## | |
6 | + # Clear all problem already resolved | |
7 | + # | |
8 | + def execute | |
9 | + nb_problem_resolved.tap { |nb| | |
10 | + if nb > 0 | |
11 | + criteria.each do |problem| | |
12 | + ProblemDestroy.new(problem).execute | |
13 | + end | |
14 | + repair_database | |
15 | + end | |
16 | + } | |
17 | + end | |
18 | + | |
19 | + private | |
20 | + | |
21 | + def nb_problem_resolved | |
22 | + @count ||= criteria.count | |
23 | + end | |
24 | + | |
25 | + def criteria | |
26 | + @criteria = Problem.resolved | |
27 | + end | |
28 | + | |
29 | + def repair_database | |
30 | + Mongoid.default_session.command :repairDatabase => 1 | |
31 | + end | |
32 | +end | ... | ... |
app/mailers/mailer.rb
... | ... | @@ -3,14 +3,20 @@ |
3 | 3 | require Rails.root.join('config/routes.rb') |
4 | 4 | |
5 | 5 | class Mailer < ActionMailer::Base |
6 | + helper ApplicationHelper | |
7 | + helper BacktraceLineHelper | |
8 | + | |
6 | 9 | default :from => Errbit::Config.email_from |
7 | 10 | |
8 | 11 | def err_notification(notice) |
9 | 12 | @notice = notice |
10 | 13 | @app = notice.app |
11 | 14 | |
15 | + count = @notice.similar_count | |
16 | + count = count > 1 ? "(#{count}) " : "" | |
17 | + | |
12 | 18 | mail :to => @app.notification_recipients, |
13 | - :subject => "[#{@app.name}][#{@notice.environment_name}] #{@notice.message}" | |
19 | + :subject => "#{count}[#{@app.name}][#{@notice.environment_name}] #{@notice.message.truncate(50)}" | |
14 | 20 | end |
15 | 21 | |
16 | 22 | def deploy_notification(deploy) |
... | ... | @@ -20,5 +26,17 @@ class Mailer < ActionMailer::Base |
20 | 26 | mail :to => @app.notification_recipients, |
21 | 27 | :subject => "[#{@app.name}] Deployed to #{@deploy.environment} by #{@deploy.username}" |
22 | 28 | end |
23 | -end | |
24 | 29 | |
30 | + def comment_notification(comment) | |
31 | + @comment = comment | |
32 | + @user = comment.user | |
33 | + @problem = comment.err | |
34 | + @notice = @problem.notices.first | |
35 | + @app = @problem.app | |
36 | + | |
37 | + recipients = @comment.notification_recipients | |
38 | + | |
39 | + mail :to => recipients, | |
40 | + :subject => "#{@user.name} commented on [#{@app.name}][#{@notice.environment_name}] #{@notice.message.truncate(50)}" | |
41 | + end | |
42 | +end | ... | ... |
app/models/app.rb
1 | 1 | class App |
2 | + include Comparable | |
2 | 3 | include Mongoid::Document |
3 | 4 | include Mongoid::Timestamps |
4 | - include Comparable | |
5 | 5 | |
6 | 6 | field :name, :type => String |
7 | 7 | field :api_key |
8 | 8 | field :github_repo |
9 | 9 | field :bitbucket_repo |
10 | + field :asset_host | |
10 | 11 | field :repository_branch |
11 | 12 | field :resolve_errs_on_deploy, :type => Boolean, :default => false |
12 | 13 | field :notify_all_users, :type => Boolean, :default => false |
... | ... | @@ -15,7 +16,12 @@ class App |
15 | 16 | field :email_at_notices, :type => Array, :default => Errbit::Config.email_at_notices |
16 | 17 | |
17 | 18 | # Some legacy apps may have string as key instead of BSON::ObjectID |
18 | - identity :type => String | |
19 | + # identity :type => String | |
20 | + field :_id, | |
21 | + type: String, | |
22 | + pre_processed: true, | |
23 | + default: ->{ Moped::BSON::ObjectId.new.to_s } | |
24 | + | |
19 | 25 | |
20 | 26 | embeds_many :watchers |
21 | 27 | embeds_many :deploys |
... | ... | @@ -41,46 +47,17 @@ class App |
41 | 47 | accepts_nested_attributes_for :notification_service, :allow_destroy => true, |
42 | 48 | :reject_if => proc { |attrs| !NotificationService.subclasses.map(&:to_s).include?(attrs[:type].to_s) } |
43 | 49 | |
44 | - # Processes a new error report. | |
45 | - # | |
46 | - # Accepts either XML or a hash with the following attributes: | |
47 | - # | |
48 | - # * <tt>:error_class</tt> - the class of error | |
49 | - # * <tt>:message</tt> - the error message | |
50 | - # * <tt>:backtrace</tt> - an array of stack trace lines | |
51 | - # | |
52 | - # * <tt>:request</tt> - a hash of values describing the request | |
53 | - # * <tt>:server_environment</tt> - a hash of values describing the server environment | |
54 | - # | |
55 | - # * <tt>:api_key</tt> - the API key with which the error was reported | |
56 | - # * <tt>:notifier</tt> - information to identify the source of the error report | |
57 | - # | |
58 | - def self.report_error!(*args) | |
59 | - report = ErrorReport.new(*args) | |
60 | - report.generate_notice! | |
61 | - end | |
62 | - | |
63 | - | |
64 | - # Processes a new error report. | |
65 | - # | |
66 | - # Accepts a hash with the following attributes: | |
50 | + # Acceps a hash with the following attributes: | |
67 | 51 | # |
68 | - # * <tt>:error_class</tt> - the class of error | |
69 | - # * <tt>:message</tt> - the error message | |
70 | - # * <tt>:backtrace</tt> - an array of stack trace lines | |
52 | + # * <tt>:error_class</tt> - the class of error (required to create a new Problem) | |
53 | + # * <tt>:environment</tt> - the environment the source app was running in (required to create a new Problem) | |
54 | + # * <tt>:fingerprint</tt> - a unique value identifying the notice | |
71 | 55 | # |
72 | - # * <tt>:request</tt> - a hash of values describing the request | |
73 | - # * <tt>:server_environment</tt> - a hash of values describing the server environment | |
74 | - # | |
75 | - # * <tt>:notifier</tt> - information to identify the source of the error report | |
76 | - # | |
77 | - def report_error!(hash) | |
78 | - report = ErrorReport.new(hash.merge(:api_key => api_key)) | |
79 | - report.generate_notice! | |
80 | - end | |
81 | - | |
82 | 56 | def find_or_create_err!(attrs) |
83 | - Err.where(:fingerprint => attrs[:fingerprint]).first || problems.create!.errs.create!(attrs) | |
57 | + Err.where( | |
58 | + :fingerprint => attrs[:fingerprint] | |
59 | + ).first || | |
60 | + problems.create!(attrs.slice(:error_class, :environment)).errs.create!(attrs.slice(:fingerprint, :problem_id)) | |
84 | 61 | end |
85 | 62 | |
86 | 63 | # Mongoid Bug: find(id) on association proxies returns an Enumerator |
... | ... | @@ -89,7 +66,7 @@ class App |
89 | 66 | end |
90 | 67 | |
91 | 68 | def self.find_by_api_key!(key) |
92 | - where(:api_key => key).first || raise(Mongoid::Errors::DocumentNotFound.new(self,key)) | |
69 | + find_by(:api_key => key) | |
93 | 70 | end |
94 | 71 | |
95 | 72 | def last_deploy_at |
... | ... | @@ -103,7 +80,7 @@ class App |
103 | 80 | end |
104 | 81 | alias :notify_on_errs? :notify_on_errs |
105 | 82 | |
106 | - def notifiable? | |
83 | + def emailable? | |
107 | 84 | notify_on_errs? && notification_recipients.any? |
108 | 85 | end |
109 | 86 | |
... | ... | @@ -125,7 +102,7 @@ class App |
125 | 102 | end |
126 | 103 | |
127 | 104 | def github_url_to_file(file) |
128 | - "#{github_url}/blob/#{repo_branch + file}" | |
105 | + "#{github_url}/blob/#{repo_branch}/#{file}" | |
129 | 106 | end |
130 | 107 | |
131 | 108 | def bitbucket_repo? |
... | ... | @@ -137,7 +114,7 @@ class App |
137 | 114 | end |
138 | 115 | |
139 | 116 | def bitbucket_url_to_file(file) |
140 | - "#{bitbucket_url}/src/#{repo_branch + file}" | |
117 | + "#{bitbucket_url}/src/#{repo_branch}/#{file}" | |
141 | 118 | end |
142 | 119 | |
143 | 120 | ... | ... |
app/models/backtrace.rb
... | ... | @@ -3,7 +3,7 @@ class Backtrace |
3 | 3 | include Mongoid::Timestamps |
4 | 4 | |
5 | 5 | field :fingerprint |
6 | - index :fingerprint | |
6 | + index :fingerprint => 1 | |
7 | 7 | |
8 | 8 | has_many :notices |
9 | 9 | has_one :notice |
... | ... | @@ -19,11 +19,11 @@ class Backtrace |
19 | 19 | end |
20 | 20 | |
21 | 21 | def similar |
22 | - Backtrace.first(:conditions => { :fingerprint => fingerprint } ) | |
22 | + Backtrace.find_by(:fingerprint => fingerprint) rescue nil | |
23 | 23 | end |
24 | 24 | |
25 | 25 | def raw=(raw) |
26 | - raw.each do |raw_line| | |
26 | + raw.compact.each do |raw_line| | |
27 | 27 | lines << BacktraceLine.new(BacktraceLineNormalizer.new(raw_line).call) |
28 | 28 | end |
29 | 29 | end | ... | ... |
app/models/backtrace_line.rb
1 | 1 | class BacktraceLine |
2 | 2 | include Mongoid::Document |
3 | - IN_APP_PATH = %r{^\[PROJECT_ROOT\]\/(?!(vendor))} | |
3 | + IN_APP_PATH = %r{^\[PROJECT_ROOT\](?!(\/vendor))/?} | |
4 | 4 | GEMS_PATH = %r{\[GEM_ROOT\]\/gems\/([^\/]+)} |
5 | 5 | |
6 | 6 | field :number, :type => Integer |
7 | + field :column, :type => Integer | |
7 | 8 | field :file |
8 | 9 | field :method |
9 | 10 | |
... | ... | @@ -14,7 +15,7 @@ class BacktraceLine |
14 | 15 | delegate :app, :to => :backtrace |
15 | 16 | |
16 | 17 | def to_s |
17 | - "#{file}:#{number}" | |
18 | + "#{file_relative}:#{number}" << (column.present? ? ":#{column}" : "") | |
18 | 19 | end |
19 | 20 | |
20 | 21 | def in_app? | ... | ... |
app/models/backtrace_line_normalizer.rb
1 | 1 | class BacktraceLineNormalizer |
2 | 2 | def initialize(raw_line) |
3 | - @raw_line = raw_line | |
3 | + @raw_line = raw_line || {} | |
4 | 4 | end |
5 | 5 | |
6 | 6 | def call |
... | ... | @@ -9,11 +9,24 @@ class BacktraceLineNormalizer |
9 | 9 | |
10 | 10 | private |
11 | 11 | def normalized_file |
12 | - @raw_line['file'].blank? ? "[unknown source]" : @raw_line['file'].to_s.gsub(/\[PROJECT_ROOT\]\/.*\/ruby\/[0-9.]+\/gems/, '[GEM_ROOT]/gems') | |
12 | + if @raw_line['file'].blank? | |
13 | + "[unknown source]" | |
14 | + else | |
15 | + file = @raw_line['file'].to_s | |
16 | + # Detect lines from gem | |
17 | + file.gsub!(/\[PROJECT_ROOT\]\/.*\/ruby\/[0-9.]+\/gems/, '[GEM_ROOT]/gems') | |
18 | + # Strip any query strings | |
19 | + file.gsub!(/\?[^\?]*$/, '') | |
20 | + @raw_line['file'] = file | |
21 | + end | |
13 | 22 | end |
14 | 23 | |
15 | 24 | def normalized_method |
16 | - @raw_line['method'].gsub(/[0-9_]{10,}+/, "__FRAGMENT__") | |
25 | + if @raw_line['method'].blank? | |
26 | + "[unknown method]" | |
27 | + else | |
28 | + @raw_line['method'].to_s.gsub(/[0-9_]{10,}+/, "__FRAGMENT__") | |
29 | + end | |
17 | 30 | end |
18 | 31 | |
19 | 32 | end | ... | ... |
app/models/comment.rb
... | ... | @@ -6,13 +6,22 @@ class Comment |
6 | 6 | before_destroy :decrease_counter_cache |
7 | 7 | |
8 | 8 | field :body, :type => String |
9 | - index :user_id | |
9 | + index(:user_id => 1) | |
10 | 10 | |
11 | 11 | belongs_to :err, :class_name => "Problem" |
12 | 12 | belongs_to :user |
13 | + delegate :app, :to => :err | |
13 | 14 | |
14 | 15 | validates_presence_of :body |
15 | 16 | |
17 | + def notification_recipients | |
18 | + app.notification_recipients - [user.email] | |
19 | + end | |
20 | + | |
21 | + def emailable? | |
22 | + app.emailable? && notification_recipients.any? | |
23 | + end | |
24 | + | |
16 | 25 | protected |
17 | 26 | def increase_counter_cache |
18 | 27 | err.inc(:comments_count, 1) |
... | ... | @@ -23,4 +32,3 @@ class Comment |
23 | 32 | end |
24 | 33 | |
25 | 34 | end |
26 | - | ... | ... |
app/models/deploy.rb
app/models/err.rb
... | ... | @@ -6,20 +6,16 @@ class Err |
6 | 6 | include Mongoid::Document |
7 | 7 | include Mongoid::Timestamps |
8 | 8 | |
9 | - field :error_class, :default => "UnknownError" | |
10 | - field :component | |
11 | - field :action | |
12 | - field :environment, :default => "unknown" | |
13 | 9 | field :fingerprint |
14 | 10 | |
15 | - belongs_to :problem | |
16 | - index :problem_id | |
17 | - index :error_class | |
18 | - index :fingerprint | |
11 | + index problem_id: 1 | |
12 | + index fingerprint: 1 | |
19 | 13 | |
14 | + belongs_to :problem | |
20 | 15 | has_many :notices, :inverse_of => :err, :dependent => :destroy |
21 | 16 | |
17 | + validates_presence_of :problem_id, :fingerprint | |
18 | + | |
22 | 19 | delegate :app, :resolved?, :to => :problem |
23 | 20 | |
24 | 21 | end |
25 | - | ... | ... |
app/models/error_report.rb
1 | -require 'digest/sha1' | |
2 | 1 | require 'hoptoad_notifier' |
3 | 2 | |
3 | +## | |
4 | +# Processes a new error report. | |
5 | +# | |
6 | +# Accepts a hash with the following attributes: | |
7 | +# | |
8 | +# * <tt>:error_class</tt> - the class of error | |
9 | +# * <tt>:message</tt> - the error message | |
10 | +# * <tt>:backtrace</tt> - an array of stack trace lines | |
11 | +# | |
12 | +# * <tt>:request</tt> - a hash of values describing the request | |
13 | +# * <tt>:server_environment</tt> - a hash of values describing the server environment | |
14 | +# | |
15 | +# * <tt>:notifier</tt> - information to identify the source of the error report | |
16 | +# | |
4 | 17 | class ErrorReport |
5 | - attr_reader :error_class, :message, :request, :server_environment, :api_key, :notifier, :user_attributes, :current_user | |
18 | + attr_reader :error_class, :message, :request, :server_environment, :api_key, :notifier, :user_attributes, :framework | |
6 | 19 | |
7 | 20 | def initialize(xml_or_attributes) |
8 | 21 | @attributes = (xml_or_attributes.is_a?(String) ? Hoptoad.parse_xml!(xml_or_attributes) : xml_or_attributes).with_indifferent_access |
9 | 22 | @attributes.each{|k, v| instance_variable_set(:"@#{k}", v) } |
10 | 23 | end |
11 | 24 | |
12 | - def fingerprint | |
13 | - @fingerprint ||= Digest::SHA1.hexdigest(fingerprint_source.to_s) | |
14 | - end | |
15 | - | |
16 | 25 | def rails_env |
17 | - server_environment['environment-name'] || 'development' | |
18 | - end | |
19 | - | |
20 | - def component | |
21 | - request['component'] || 'unknown' | |
22 | - end | |
23 | - | |
24 | - def action | |
25 | - request['action'] | |
26 | + rails_env = server_environment['environment-name'] | |
27 | + rails_env = 'development' if rails_env.blank? | |
28 | + rails_env | |
26 | 29 | end |
27 | 30 | |
28 | 31 | def app |
29 | - @app ||= App.find_by_api_key!(api_key) | |
32 | + @app ||= App.where(:api_key => api_key).first | |
30 | 33 | end |
31 | 34 | |
32 | 35 | def backtrace |
... | ... | @@ -34,7 +37,9 @@ class ErrorReport |
34 | 37 | end |
35 | 38 | |
36 | 39 | def generate_notice! |
37 | - notice = Notice.new( | |
40 | + return unless valid? | |
41 | + return @notice if @notice | |
42 | + @notice = Notice.new( | |
38 | 43 | :message => message, |
39 | 44 | :error_class => error_class, |
40 | 45 | :backtrace_id => backtrace.id, |
... | ... | @@ -42,31 +47,35 @@ class ErrorReport |
42 | 47 | :server_environment => server_environment, |
43 | 48 | :notifier => notifier, |
44 | 49 | :user_attributes => user_attributes, |
45 | - :current_user => current_user | |
50 | + :framework => framework | |
46 | 51 | ) |
52 | + error.notices << @notice | |
53 | + @notice | |
54 | + end | |
55 | + attr_reader :notice | |
47 | 56 | |
48 | - err = app.find_or_create_err!( | |
57 | + ## | |
58 | + # Error associate to this error_report | |
59 | + # | |
60 | + # Can already exist or not | |
61 | + # | |
62 | + # @return [ Error ] | |
63 | + def error | |
64 | + @error ||= app.find_or_create_err!( | |
49 | 65 | :error_class => error_class, |
50 | - :component => component, | |
51 | - :action => action, | |
52 | 66 | :environment => rails_env, |
53 | - :fingerprint => fingerprint) | |
67 | + :fingerprint => fingerprint | |
68 | + ) | |
69 | + end | |
54 | 70 | |
55 | - err.notices << notice | |
56 | - notice | |
71 | + def valid? | |
72 | + !!app | |
57 | 73 | end |
58 | 74 | |
59 | 75 | private |
60 | - def fingerprint_source | |
61 | - { | |
62 | - :backtrace => backtrace.id, | |
63 | - :error_class => error_class, | |
64 | - :component => component, | |
65 | - :action => action, | |
66 | - :environment => rails_env, | |
67 | - :api_key => api_key | |
68 | - } | |
76 | + | |
77 | + def fingerprint | |
78 | + @fingerprint ||= Fingerprint.generate(notice, api_key) | |
69 | 79 | end |
70 | 80 | |
71 | 81 | end |
72 | - | ... | ... |
... | ... | @@ -0,0 +1,41 @@ |
1 | +require 'digest/sha1' | |
2 | + | |
3 | +class Fingerprint | |
4 | + attr_reader :notice, :api_key | |
5 | + | |
6 | + def self.generate(notice, api_key) | |
7 | + self.new(notice, api_key).to_s | |
8 | + end | |
9 | + | |
10 | + def initialize(notice, api_key) | |
11 | + @notice = notice | |
12 | + @api_key = api_key | |
13 | + end | |
14 | + | |
15 | + | |
16 | + | |
17 | + def to_s | |
18 | + Digest::SHA1.hexdigest(fingerprint_source.to_s) | |
19 | + end | |
20 | + | |
21 | + def fingerprint_source | |
22 | + # Find the first backtrace line with a file and line number. | |
23 | + if line = notice.backtrace.lines.detect {|l| l.number.present? && l.file.present? } | |
24 | + # If line exists, only use file and number. | |
25 | + file_or_message = "#{line.file}:#{line.number}" | |
26 | + else | |
27 | + # If no backtrace, use error message | |
28 | + file_or_message = notice.message | |
29 | + end | |
30 | + | |
31 | + { | |
32 | + :file_or_message => file_or_message, | |
33 | + :error_class => notice.error_class, | |
34 | + :component => notice.component || 'unknown', | |
35 | + :action => notice.action, | |
36 | + :environment => notice.environment_name || 'development', | |
37 | + :api_key => api_key | |
38 | + } | |
39 | + end | |
40 | + | |
41 | +end | ... | ... |
app/models/issue_tracker.rb
... | ... | @@ -15,6 +15,15 @@ class IssueTracker |
15 | 15 | field :password, :type => String |
16 | 16 | field :ticket_properties, :type => String |
17 | 17 | field :subdomain, :type => String |
18 | + field :milestone_id, :type => String | |
19 | + | |
20 | + # Is there any better way to enhance the props? Putting them into the subclass leads to | |
21 | + # an error while rendering the form fields -.- | |
22 | + field :base_url, :type => String | |
23 | + field :context_path, :type => String | |
24 | + field :issue_type, :type => String | |
25 | + field :issue_component, :type => String | |
26 | + field :issue_priority, :type => String | |
18 | 27 | |
19 | 28 | validate :check_params |
20 | 29 | |
... | ... | @@ -39,5 +48,15 @@ class IssueTracker |
39 | 48 | def configured? |
40 | 49 | project_id.present? |
41 | 50 | end |
42 | -end | |
43 | 51 | |
52 | + ## | |
53 | + # Update default_url_option with valid data from the request information | |
54 | + # | |
55 | + # @param [ Request ] a request with host, port and protocol | |
56 | + # | |
57 | + def self.update_url_options(request) | |
58 | + IssueTracker.default_url_options[:host] = request.host | |
59 | + IssueTracker.default_url_options[:port] = request.port | |
60 | + IssueTracker.default_url_options[:protocol] = request.scheme | |
61 | + end | |
62 | +end | ... | ... |
app/models/issue_trackers/bitbucket_issues_tracker.rb
1 | -class IssueTrackers::BitbucketIssuesTracker < IssueTracker | |
2 | - Label = "bitbucket" | |
3 | - Note = 'Please configure your Bitbucket repository in the <strong>BITBUCKET REPO</strong> field above.' | |
4 | - Fields = [ | |
5 | - [:api_token, { | |
6 | - :placeholder => "Your username on Bitbucket account", | |
7 | - :label => "Username" | |
8 | - }], | |
9 | - [:project_id, { | |
10 | - :placeholder => "Password for your Bitbucket account", | |
11 | - :label => "Password" | |
12 | - }] | |
13 | - ] | |
1 | +begin | |
2 | + require 'bitbucket_rest_api' | |
3 | +rescue LoadError | |
4 | +end | |
5 | + | |
6 | +if defined? BitBucket | |
7 | + class IssueTrackers::BitbucketIssuesTracker < IssueTracker | |
8 | + Label = "bitbucket" | |
9 | + Note = 'Please configure your Bitbucket repository in the <strong>BITBUCKET REPO</strong> field above.' | |
10 | + Fields = [ | |
11 | + [:api_token, { | |
12 | + :placeholder => "Your username on Bitbucket account", | |
13 | + :label => "Username" | |
14 | + }], | |
15 | + [:project_id, { | |
16 | + :placeholder => "Password for your Bitbucket account", | |
17 | + :label => "Password" | |
18 | + }] | |
19 | + ] | |
14 | 20 | |
15 | - def check_params | |
16 | - if Fields.detect {|f| self[f[0]].blank? } | |
17 | - errors.add :base, 'You must specify your Bitbucket username and password' | |
21 | + def check_params | |
22 | + if Fields.detect {|f| self[f[0]].blank? } | |
23 | + errors.add :base, 'You must specify your Bitbucket username and password' | |
24 | + end | |
18 | 25 | end |
19 | - end | |
20 | 26 | |
21 | - def repo_name | |
22 | - app.bitbucket_repo | |
23 | - end | |
27 | + def repo_name | |
28 | + app.bitbucket_repo | |
29 | + end | |
24 | 30 | |
25 | - def create_issue(problem, reported_by = nil) | |
26 | - bitbucket = BitBucket.new :basic_auth => "#{api_token}:#{project_id}" | |
31 | + def create_issue(problem, reported_by = nil) | |
32 | + bitbucket = BitBucket.new :basic_auth => "#{api_token}:#{project_id}" | |
27 | 33 | |
28 | - begin | |
29 | - issue = bitbucket.issues.create api_token, repo_name.split('/')[1], :title => issue_title(problem), :content => body_template.result(binding), :priority => 'critical' | |
30 | - problem.update_attributes( | |
31 | - :issue_link => "https://bitbucket.org/#{repo_name}/issue/#{issue.local_id}/", | |
32 | - :issue_type => Label | |
33 | - ) | |
34 | - rescue BitBucket::Error::Unauthorized | |
35 | - raise IssueTrackers::AuthenticationError, "Could not authenticate with BitBucket. Please check your username and password." | |
34 | + begin | |
35 | + r_user = repo_name.split('/')[0] | |
36 | + r_name = repo_name.split('/')[1] | |
37 | + issue = bitbucket.issues.create r_user, r_name, :title => issue_title(problem), :content => body_template.result(binding), :priority => 'critical' | |
38 | + problem.update_attributes( | |
39 | + :issue_link => "https://bitbucket.org/#{repo_name}/issue/#{issue.local_id}/", | |
40 | + :issue_type => Label | |
41 | + ) | |
42 | + rescue BitBucket::Error::Unauthorized | |
43 | + raise IssueTrackers::AuthenticationError, "Could not authenticate with BitBucket. Please check your username and password." | |
44 | + end | |
36 | 45 | end |
37 | - end | |
38 | 46 | |
39 | - def body_template | |
40 | - @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/bitbucket_issues_body.txt.erb")) | |
41 | - end | |
47 | + def body_template | |
48 | + @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/bitbucket_issues_body.txt.erb")) | |
49 | + end | |
42 | 50 | |
43 | - def url | |
44 | - "https://www.bitbucket.org/#{repo_name}/issues" | |
51 | + def url | |
52 | + "https://www.bitbucket.org/#{repo_name}/issues" | |
53 | + end | |
45 | 54 | end |
46 | 55 | end |
47 | - | ... | ... |
app/models/issue_trackers/gitlab_tracker.rb
... | ... | @@ -10,7 +10,7 @@ if defined? Gitlab |
10 | 10 | :placeholder => "API Token for your account" |
11 | 11 | }], |
12 | 12 | [:project_id, { |
13 | - :label => "Ticket Project Short Name / ID", | |
13 | + :label => "Ticket Project ID (use Number)", | |
14 | 14 | :placeholder => "Gitlab Project where issues will be created" |
15 | 15 | }] |
16 | 16 | ] |
... | ... | @@ -23,19 +23,25 @@ if defined? Gitlab |
23 | 23 | |
24 | 24 | def create_issue(problem, reported_by = nil) |
25 | 25 | Gitlab.configure do |config| |
26 | - config.endpoint = "#{account}/api/v2" | |
26 | + config.endpoint = "#{account}/api/v3" | |
27 | 27 | config.private_token = api_token |
28 | 28 | config.user_agent = 'Errbit User Agent' |
29 | 29 | end |
30 | 30 | title = issue_title problem |
31 | - description = body_template.result(binding) | |
32 | - Gitlab.create_issue(project_id, title, { :description => description, :labels => "errbit" } ) | |
31 | + description_summary = summary_template.result(binding) | |
32 | + description_body = body_template.result(binding) | |
33 | + ticket = Gitlab.create_issue(project_id, title, { :description => description_summary, :labels => "errbit" } ) | |
34 | + Gitlab.create_issue_note(project_id, ticket.id, description_body) | |
35 | + end | |
36 | + | |
37 | + def summary_template | |
38 | + @@summary_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/gitlab_summary.txt.erb").gsub(/^\s*/, '')) | |
33 | 39 | end |
34 | 40 | |
35 | 41 | def body_template |
36 | 42 | @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/gitlab_body.txt.erb").gsub(/^\s*/, '')) |
37 | 43 | end |
38 | - | |
44 | + | |
39 | 45 | def url |
40 | 46 | "#{account}/#{project_id}/issues" |
41 | 47 | end | ... | ... |
... | ... | @@ -0,0 +1,110 @@ |
1 | +if defined? JIRA | |
2 | + class IssueTrackers::JiraTracker < IssueTracker | |
3 | + Label = 'jira' | |
4 | + | |
5 | + Fields = [ | |
6 | + [:base_url, { | |
7 | + :label => 'Jira URL without trailing slash', | |
8 | + :placeholder => 'https://jira.example.org/' | |
9 | + }], | |
10 | + [:context_path, { | |
11 | + :optional => true, | |
12 | + :label => 'Context Path (Just "/" if empty otherwise with leading slash)', | |
13 | + :placeholder => "/jira" | |
14 | + }], | |
15 | + [:username, { | |
16 | + :optional => true, | |
17 | + :label => 'HTTP Basic Auth User', | |
18 | + :placeholder => 'johndoe' | |
19 | + }], | |
20 | + [:password, { | |
21 | + :optional => true, | |
22 | + :label => 'HTTP Basic Auth Password', | |
23 | + :placeholder => 'p@assW0rd' | |
24 | + }], | |
25 | + [:project_id, { | |
26 | + :label => 'Project Key', | |
27 | + :placeholder => 'The project Key where the issue will be created' | |
28 | + }], | |
29 | + [:account, { | |
30 | + :optional => true, | |
31 | + :label => 'Assign to this user. If empty, Jira takes the project default.', | |
32 | + :placeholder => "username" | |
33 | + }], | |
34 | + [:issue_component, { | |
35 | + :label => 'Issue category', | |
36 | + :placeholder => 'Website - Other' | |
37 | + }], | |
38 | + [:issue_type, { | |
39 | + :label => 'Issue type', | |
40 | + :placeholder => 'Bug' | |
41 | + }], | |
42 | + [:issue_priority, { | |
43 | + :label => 'Priority', | |
44 | + :placeholder => 'Normal' | |
45 | + }] | |
46 | + ] | |
47 | + | |
48 | + def check_params | |
49 | + if Fields.detect { |f| self[f[0]].blank? && !f[1][:optional] } | |
50 | + errors.add :base, 'You must specify all non optional values!' | |
51 | + end | |
52 | + end | |
53 | + | |
54 | + | |
55 | + # | |
56 | + # @param problem Problem | |
57 | + def create_issue(problem, reported_by = nil) | |
58 | + options = { | |
59 | + :username => username, | |
60 | + :password => password, | |
61 | + :site => base_url, | |
62 | + :context_path => context_path, | |
63 | + :auth_type => :basic, | |
64 | + :use_ssl => base_url.match(/^https/) ? true : false | |
65 | + } | |
66 | + client = JIRA::Client.new(options) | |
67 | + | |
68 | + issue = { | |
69 | + :fields => { | |
70 | + :project => { | |
71 | + :key => project_id | |
72 | + }, | |
73 | + :summary => issue_title(problem), | |
74 | + :description => body_template.result(binding), | |
75 | + :issuetype => { | |
76 | + :name => issue_type | |
77 | + }, | |
78 | + :priority => { | |
79 | + :name => issue_priority, | |
80 | + }, | |
81 | + | |
82 | + :components => [{:name => issue_component}] | |
83 | + } | |
84 | + } | |
85 | + | |
86 | + issue[:fields][:assignee] = {:name => account} if account | |
87 | + | |
88 | + issue_build = client.Issue.build | |
89 | + issue_build.save(issue) | |
90 | + issue_build.fetch | |
91 | + | |
92 | + problem.update_attributes( | |
93 | + :issue_link => "#{base_url}#{context_path}browse/#{issue_build.key}", | |
94 | + :issue_type => Label | |
95 | + ) | |
96 | + | |
97 | + # Maybe in a later version? | |
98 | + #remote_link = { | |
99 | + # :url => app_problem_url(problem.app, problem), | |
100 | + # :name => "Link to Errbit Issue" | |
101 | + #} | |
102 | + #remote_link_build = issue_build.remotelink.build | |
103 | + #remote_link_build.save(remote_link) | |
104 | + end | |
105 | + | |
106 | + def body_template | |
107 | + @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/jira_body.txt.erb")) | |
108 | + end | |
109 | + end | |
110 | +end | |
0 | 111 | \ No newline at end of file | ... | ... |
app/models/issue_trackers/redmine_tracker.rb
... | ... | @@ -9,6 +9,12 @@ if defined? RedmineClient |
9 | 9 | [:api_token, { |
10 | 10 | :placeholder => "API Token for your account" |
11 | 11 | }], |
12 | + [:username, { | |
13 | + :placeholder => "Your username" | |
14 | + }], | |
15 | + [:password, { | |
16 | + :placeholder => "Your password" | |
17 | + }], | |
12 | 18 | [:project_id, { |
13 | 19 | :label => "Ticket Project", |
14 | 20 | :placeholder => "Redmine Project where tickets will be created" |
... | ... | @@ -22,15 +28,19 @@ if defined? RedmineClient |
22 | 28 | |
23 | 29 | def check_params |
24 | 30 | if Fields.detect {|f| self[f[0]].blank? && !f[1][:optional]} |
25 | - errors.add :base, 'You must specify your Redmine URL, API token and Project ID' | |
31 | + errors.add :base, 'You must specify your Redmine URL, API token, Username, Password and Project ID' | |
26 | 32 | end |
27 | 33 | end |
28 | 34 | |
29 | 35 | def create_issue(problem, reported_by = nil) |
30 | 36 | token = api_token |
31 | 37 | acc = account |
38 | + user = username | |
39 | + passwd = password | |
32 | 40 | RedmineClient::Base.configure do |
33 | 41 | self.token = token |
42 | + self.user = user | |
43 | + self.password = passwd | |
34 | 44 | self.site = acc |
35 | 45 | self.format = :xml |
36 | 46 | end |
... | ... | @@ -47,17 +57,18 @@ if defined? RedmineClient |
47 | 57 | def url_to_file(file_path, line_number = nil) |
48 | 58 | # alt_project_id let's users specify a different project for tickets / app files. |
49 | 59 | project = self.alt_project_id.present? ? self.alt_project_id : self.project_id |
50 | - url = "#{self.account}/projects/#{project}/repository/annotate/#{file_path.sub(/^\//,'')}" | |
60 | + url = "#{self.account.gsub(/\/$/, '')}/projects/#{project}/repository/revisions/#{app.repository_branch}/changes/#{file_path.sub(/\[PROJECT_ROOT\]/, '').sub(/^\//,'')}" | |
51 | 61 | line_number ? url << "#L#{line_number}" : url |
52 | 62 | end |
53 | 63 | |
54 | 64 | def body_template |
55 | - @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/textile_body.txt.erb")) | |
65 | + @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/redmine_body.txt.erb")) | |
56 | 66 | end |
57 | 67 | |
58 | 68 | def url |
59 | 69 | acc_url = account.start_with?('http') ? account : "http://#{account}" |
60 | - URI.parse("#{acc_url}?project_id=#{project_id}").to_s | |
70 | + acc_url = acc_url.gsub(/\/$/, '') | |
71 | + URI.parse("#{acc_url}/projects/#{project_id}").to_s | |
61 | 72 | rescue URI::InvalidURIError |
62 | 73 | end |
63 | 74 | end | ... | ... |
... | ... | @@ -0,0 +1,69 @@ |
1 | +class IssueTrackers::UnfuddleTracker < IssueTracker | |
2 | + Label = "unfuddle" | |
3 | + Fields = [ | |
4 | + | |
5 | + [:account, { | |
6 | + :placeholder => "Your domain" | |
7 | + }], | |
8 | + | |
9 | + | |
10 | + [:username, { | |
11 | + :placeholder => "Your username" | |
12 | + }], | |
13 | + | |
14 | + [:password, { | |
15 | + :placeholder => "Your password" | |
16 | + }], | |
17 | + | |
18 | + [:project_id, { | |
19 | + :label => "Ticket Project", | |
20 | + :placeholder => "Project where tickets will be created" | |
21 | + }], | |
22 | + | |
23 | + [:milestone_id, { | |
24 | + :optional => true, | |
25 | + :label => "Ticket Milestone", | |
26 | + :placeholder => "Milestone where tickets will be created" | |
27 | + }] | |
28 | + | |
29 | + | |
30 | + ] | |
31 | + | |
32 | + def check_params | |
33 | + if Fields.detect {|f| self[f[0]].blank? && !f[1][:optional]} | |
34 | + errors.add :base, 'You must specify your Account, Username, Password and Project ID' | |
35 | + end | |
36 | + end | |
37 | + | |
38 | + def create_issue(problem, reported_by = nil) | |
39 | + unfuddle = TaskMapper.new(:unfuddle, :username => username, :password => password, :account => account) | |
40 | + | |
41 | + begin | |
42 | + issue_options = {:project_id => project_id, | |
43 | + :summary => issue_title(problem), | |
44 | + :priority => '5', | |
45 | + :status => "new", | |
46 | + :description => body_template.result(binding), | |
47 | + 'description-format' => 'textile' } | |
48 | + | |
49 | + issue_options[:milestone_id] = milestone_id if milestone_id.present? | |
50 | + | |
51 | + issue = unfuddle.project(project_id.to_i).ticket!(issue_options) | |
52 | + problem.update_attributes( | |
53 | + :issue_link => File.join("#{url}/tickets/#{issue['id']}"), | |
54 | + :issue_type => Label | |
55 | + ) | |
56 | + rescue ActiveResource::UnauthorizedAccess | |
57 | + raise ActiveResource::UnauthorizedAccess, "Could not authenticate with Unfuddle. Please check your username and password." | |
58 | + end | |
59 | + | |
60 | + end | |
61 | + | |
62 | + def body_template | |
63 | + @@body_template ||= ERB.new(File.read(Rails.root + "app/views/issue_trackers/textile_body.txt.erb")) | |
64 | + end | |
65 | + | |
66 | + def url | |
67 | + "https://#{account}.unfuddle.com/projects/#{project_id}" | |
68 | + end | |
69 | +end | ... | ... |
app/models/notice.rb
1 | -require 'hoptoad' | |
2 | 1 | require 'recurse' |
3 | 2 | |
4 | 3 | class Notice |
... | ... | @@ -10,23 +9,20 @@ class Notice |
10 | 9 | field :request, :type => Hash |
11 | 10 | field :notifier, :type => Hash |
12 | 11 | field :user_attributes, :type => Hash |
13 | - field :current_user, :type => Hash | |
12 | + field :framework | |
14 | 13 | field :error_class |
15 | 14 | delegate :lines, :to => :backtrace, :prefix => true |
16 | 15 | delegate :app, :problem, :to => :err |
17 | 16 | |
18 | 17 | belongs_to :err |
19 | 18 | belongs_to :backtrace, :index => true |
20 | - index :created_at | |
21 | - index( | |
22 | - [ | |
23 | - [ :err_id, Mongo::ASCENDING ], | |
24 | - [ :created_at, Mongo::ASCENDING ], | |
25 | - [ :_id, Mongo::ASCENDING ] | |
26 | - ] | |
27 | - ) | |
28 | - | |
29 | - after_create :increase_counter_cache, :cache_attributes_on_problem, :unresolve_problem | |
19 | + | |
20 | + index(:created_at => 1) | |
21 | + index(:err_id => 1, :created_at => 1, :_id => 1) | |
22 | + | |
23 | + after_create :cache_attributes_on_problem, :unresolve_problem | |
24 | + after_create :email_notification | |
25 | + after_create :services_notification | |
30 | 26 | before_save :sanitize |
31 | 27 | before_destroy :decrease_counter_cache, :remove_cached_attributes_from_problem |
32 | 28 | |
... | ... | @@ -42,7 +38,11 @@ class Notice |
42 | 38 | end |
43 | 39 | |
44 | 40 | def user_agent_string |
45 | - (user_agent.nil? || user_agent.none?) ? "N/A" : "#{user_agent.browser} #{user_agent.version}" | |
41 | + if user_agent.nil? || user_agent.none? | |
42 | + "N/A" | |
43 | + else | |
44 | + "#{user_agent.browser} #{user_agent.version} (#{user_agent.os})" | |
45 | + end | |
46 | 46 | end |
47 | 47 | |
48 | 48 | def environment_name |
... | ... | @@ -98,20 +98,29 @@ class Notice |
98 | 98 | problem.notices_count |
99 | 99 | end |
100 | 100 | |
101 | - def notifiable? | |
101 | + def emailable? | |
102 | 102 | app.email_at_notices.include?(similar_count) |
103 | 103 | end |
104 | 104 | |
105 | - def should_notify? | |
106 | - app.notifiable? && notifiable? | |
105 | + def should_email? | |
106 | + app.emailable? && emailable? | |
107 | 107 | end |
108 | 108 | |
109 | - protected | |
109 | + def should_notify? | |
110 | + app.notification_service.notify_at_notices.include?(0) || app.notification_service.notify_at_notices.include?(similar_count) | |
111 | + end | |
110 | 112 | |
111 | - def increase_counter_cache | |
112 | - problem.inc(:notices_count, 1) | |
113 | + ## | |
114 | + # TODO: Move on decorator maybe | |
115 | + # | |
116 | + def project_root | |
117 | + if server_environment | |
118 | + server_environment['project-root'] || '' | |
119 | + end | |
113 | 120 | end |
114 | 121 | |
122 | + protected | |
123 | + | |
115 | 124 | def decrease_counter_cache |
116 | 125 | problem.inc(:notices_count, -1) if err |
117 | 126 | end |
... | ... | @@ -125,7 +134,7 @@ class Notice |
125 | 134 | end |
126 | 135 | |
127 | 136 | def cache_attributes_on_problem |
128 | - problem.cache_notice_attributes(self) | |
137 | + ProblemUpdaterCache.new(problem, self).update | |
129 | 138 | end |
130 | 139 | |
131 | 140 | def sanitize |
... | ... | @@ -134,6 +143,7 @@ class Notice |
134 | 143 | end |
135 | 144 | end |
136 | 145 | |
146 | + | |
137 | 147 | def sanitize_hash(h) |
138 | 148 | h.recurse do |
139 | 149 | |h| h.inject({}) do |h,(k,v)| |
... | ... | @@ -147,5 +157,25 @@ class Notice |
147 | 157 | end |
148 | 158 | end |
149 | 159 | |
160 | + private | |
161 | + | |
162 | + ## | |
163 | + # Send email notification if needed | |
164 | + def email_notification | |
165 | + return true unless should_email? | |
166 | + Mailer.err_notification(self).deliver | |
167 | + rescue => e | |
168 | + HoptoadNotifier.notify(e) | |
169 | + end | |
170 | + | |
171 | + ## | |
172 | + # Launch all notification define on the app associate to this notice | |
173 | + def services_notification | |
174 | + return true unless app.notification_service_configured? and should_notify? | |
175 | + app.notification_service.create_notification(problem) | |
176 | + rescue => e | |
177 | + HoptoadNotifier.notify(e) | |
178 | + end | |
179 | + | |
150 | 180 | end |
151 | 181 | ... | ... |
app/models/notice_observer.rb
... | ... | @@ -1,13 +0,0 @@ |
1 | -class NoticeObserver < Mongoid::Observer | |
2 | - observe :notice | |
3 | - | |
4 | - def after_create notice | |
5 | - # if the app has a notification service, fire it off | |
6 | - if notice.app.notification_service_configured? | |
7 | - notice.app.notification_service.create_notification(notice.problem) | |
8 | - end | |
9 | - | |
10 | - Mailer.err_notification(notice).deliver if notice.should_notify? | |
11 | - end | |
12 | - | |
13 | -end |
app/models/notification_service.rb
... | ... | @@ -5,14 +5,31 @@ class NotificationService |
5 | 5 | default_url_options[:host] = ActionMailer::Base.default_url_options[:host] |
6 | 6 | |
7 | 7 | field :room_id, :type => String |
8 | + field :user_id, :type => String | |
9 | + field :service_url, :type => String | |
10 | + field :service, :type => String | |
8 | 11 | field :api_token, :type => String |
9 | 12 | field :subdomain, :type => String |
10 | 13 | field :sender_name, :type => String |
11 | - | |
14 | + field :notify_at_notices, :type => Array, :default => Errbit::Config.notify_at_notices | |
12 | 15 | embedded_in :app, :inverse_of => :notification_service |
13 | 16 | |
14 | 17 | validate :check_params |
15 | 18 | |
19 | + if Errbit::Config.per_app_notify_at_notices | |
20 | + Fields = [[:notify_at_notices, | |
21 | + { :placeholder => 'comma separated numbers or simply 0 for every notice', | |
22 | + :label => 'notify on errors (0 for all errors)' | |
23 | + } | |
24 | + ]] | |
25 | + else | |
26 | + Fields = [] | |
27 | + end | |
28 | + | |
29 | + def notify_at_notices | |
30 | + Errbit::Config.per_app_notify_at_notices ? super : Errbit::Config.notify_at_notices | |
31 | + end | |
32 | + | |
16 | 33 | # Subclasses are responsible for overwriting this method. |
17 | 34 | def check_params; true; end |
18 | 35 | |
... | ... | @@ -34,4 +51,8 @@ class NotificationService |
34 | 51 | def configured? |
35 | 52 | api_token.present? |
36 | 53 | end |
54 | + | |
55 | + def problem_url(problem) | |
56 | + "http://#{Errbit::Config.host}/apps/#{problem.app.id}/problems/#{problem.id}" | |
57 | + end | |
37 | 58 | end | ... | ... |
app/models/notification_services/campfire_service.rb
1 | 1 | if defined? Campy |
2 | 2 | class NotificationServices::CampfireService < NotificationService |
3 | 3 | Label = "campfire" |
4 | - Fields = [ | |
4 | + Fields += [ | |
5 | 5 | [:subdomain, { |
6 | 6 | :label => "Subdomain", |
7 | 7 | :placeholder => "subdomain from http://{{subdomain}}.campfirenow.com" |
... | ... | @@ -33,4 +33,4 @@ if defined? Campy |
33 | 33 | campy.speak "[errbit] #{problem.app.name} #{notification_description problem} - http://#{Errbit::Config.host}/apps/#{problem.app.id.to_s}/problems/#{problem.id.to_s}" |
34 | 34 | end |
35 | 35 | end |
36 | -end | |
37 | 36 | \ No newline at end of file |
37 | +end | ... | ... |
... | ... | @@ -0,0 +1,45 @@ |
1 | +if defined? Flowdock | |
2 | + class NotificationServices::FlowdockService < NotificationService | |
3 | + Label = 'flowdock' | |
4 | + Fields += [ | |
5 | + [ | |
6 | + :api_token, { | |
7 | + :label => 'Flow API Token', | |
8 | + :placeholder => '123456789abcdef123456789abcdefgh' | |
9 | + } | |
10 | + ] | |
11 | + ] | |
12 | + | |
13 | + def check_params | |
14 | + if Fields.any? { |f, _| self[f].blank? } | |
15 | + errors.add :base, 'You must specify your Flowdock(Flow) API token' | |
16 | + end | |
17 | + end | |
18 | + | |
19 | + def url | |
20 | + 'https://www.flowdock.com/session' | |
21 | + end | |
22 | + | |
23 | + def create_notification(problem) | |
24 | + flow = Flowdock::Flow.new(:api_token => api_token, :source => "Errbit", :from => {:name => "Errbit", :address => ENV['ERRBIT_EMAIL_FROM'] || 'support@flowdock.com'}) | |
25 | + subject = "[#{problem.environment}] #{problem.message.to_s.truncate(100)}" | |
26 | + url = app_problem_url problem.app, problem | |
27 | + flow.push_to_team_inbox(:subject => subject, :content => content(problem, url), :project => project_name(problem), :link => url) | |
28 | + end | |
29 | + | |
30 | + private | |
31 | + | |
32 | + # can only contain alphanumeric characters and underscores | |
33 | + def project_name(problem) | |
34 | + problem.app.name.gsub /[^0-9a-z_]/i, '' | |
35 | + end | |
36 | + | |
37 | + def content(problem, url) | |
38 | + full_description = "[#{ problem.environment }][#{ problem.where }] #{problem.message.to_s}" | |
39 | + <<-MSG.strip_heredoc | |
40 | + #{ERB::Util.html_escape full_description}<br> | |
41 | + <a href="#{url}">#{url}</a> | |
42 | + MSG | |
43 | + end | |
44 | + end | |
45 | +end | ... | ... |
app/models/notification_services/gtalk_service.rb
1 | 1 | class NotificationServices::GtalkService < NotificationService |
2 | 2 | Label = "gtalk" |
3 | - Fields = [ | |
3 | + Fields += [ | |
4 | 4 | [:subdomain, { |
5 | 5 | :placeholder => "username@example.com", |
6 | 6 | :label => "Username" |
... | ... | @@ -9,29 +9,64 @@ class NotificationServices::GtalkService < NotificationService |
9 | 9 | :placeholder => "password", |
10 | 10 | :label => "Password" |
11 | 11 | }], |
12 | + [:user_id, { | |
13 | + :placeholder => "touser@example.com, anotheruser@example.com", | |
14 | + :label => "Send To User(s)" | |
15 | + }, :room_id], | |
12 | 16 | [:room_id, { |
13 | - :placeholder => "touser@example.com", | |
14 | - :label => "Send To User" | |
17 | + :placeholder => "toroom@conference.example.com", | |
18 | + :label => "Send To Room (one only)" | |
19 | + }, :user_id], | |
20 | + [ :service, { | |
21 | + :placeholder => "talk.google.com", | |
22 | + :label => "Jabber Service" | |
15 | 23 | }], |
24 | + [ :service_url, { | |
25 | + :placeholder => "http://www.google.com/talk/", | |
26 | + :label => "Link To Jabber Service" | |
27 | + }] | |
16 | 28 | ] |
17 | 29 | |
18 | 30 | def check_params |
19 | - if Fields.detect {|f| self[f[0]].blank? } | |
20 | - errors.add :base, 'You must specify your Username, Password and To User' | |
31 | + if Fields.detect { |f| self[f[0]].blank? && self[f[2]].blank? } | |
32 | + errors.add :base, | |
33 | + """You must specify your Username, Password, service, service_url | |
34 | + and either rooms or users to send to or both""" | |
21 | 35 | end |
22 | 36 | end |
23 | 37 | |
24 | 38 | def url |
25 | - "http://www.google.com/talk/" | |
39 | + service_url || "http://www.google.com/talk/" | |
26 | 40 | end |
27 | 41 | |
28 | 42 | def create_notification(problem) |
29 | 43 | # build the xmpp client |
30 | 44 | client = Jabber::Client.new(Jabber::JID.new(subdomain)) |
31 | - client.connect("talk.google.com") | |
45 | + client.connect(service) | |
32 | 46 | client.auth(api_token) |
33 | 47 | |
34 | - # post the issue to the xmpp room | |
35 | - client.send(Jabber::Message.new(room_id, "[errbit] http://#{Errbit::Config.host}/apps/#{problem.app.id.to_s} #{notification_description problem}")) | |
48 | + #has to look like this to be formatted properly in the client | |
49 | + message = """#{problem.app.name.to_s} | |
50 | +http://#{Errbit::Config.host}/apps/#{problem.app.id.to_s} | |
51 | +#{notification_description problem}""" | |
52 | + | |
53 | + # post the issue to the xmpp room(s) | |
54 | + send_to_users(client, message) unless user_id.blank? | |
55 | + send_to_muc(client, message) unless room_id.blank? | |
56 | + end | |
57 | + | |
58 | + private | |
59 | + | |
60 | + def send_to_users client, message | |
61 | + user_id.gsub(/ /i, ",").gsub(/;/i, ",").split(",").map(&:strip).reject(&:empty?).each do |user| | |
62 | + client.send(Jabber::Message.new(user, message)) | |
63 | + end | |
64 | + end | |
65 | + | |
66 | + def send_to_muc client, message | |
67 | + #TODO: set this so that it can send to multiple rooms like users, nb multiple room joins in one send fail randomly so leave as one room for the moment | |
68 | + muc = Jabber::MUC::SimpleMUCClient.new(client) | |
69 | + muc.join(room_id + "/errbit") | |
70 | + muc.send(Jabber::Message.new(room_id, message)) | |
36 | 71 | end |
37 | -end | |
38 | 72 | \ No newline at end of file |
73 | +end | ... | ... |
app/models/notification_services/hipchat_service.rb
1 | 1 | if defined? HipChat |
2 | 2 | class NotificationServices::HipchatService < NotificationService |
3 | 3 | Label = 'hipchat' |
4 | - Fields = [ | |
4 | + Fields += [ | |
5 | 5 | [:api_token, { |
6 | 6 | :placeholder => "API Token" |
7 | 7 | }], |
... | ... | @@ -24,8 +24,9 @@ if defined? HipChat |
24 | 24 | def create_notification(problem) |
25 | 25 | url = app_problem_url problem.app, problem |
26 | 26 | message = <<-MSG.strip_heredoc |
27 | - [#{ERB::Util.html_escape problem.app.name}]#{ERB::Util.html_escape notification_description(problem)}<br> | |
28 | - <a href="#{url}">#{url}</a> | |
27 | + <strong>#{ERB::Util.html_escape problem.app.name}</strong> error in <strong>#{ERB::Util.html_escape problem.environment}</strong> at <strong>#{ERB::Util.html_escape problem.where}</strong> (<a href="#{url}">details</a>)<br> | |
28 | + #{ERB::Util.html_escape problem.message.to_s.truncate(100)}<br> | |
29 | + Times occurred: #{problem.notices_count} | |
29 | 30 | MSG |
30 | 31 | |
31 | 32 | client = HipChat::Client.new(api_token) | ... | ... |
app/models/notification_services/hoiio_service.rb
1 | 1 | class NotificationServices::HoiioService < NotificationService |
2 | 2 | Label = "hoiio" |
3 | - Fields = [ | |
3 | + Fields += [ | |
4 | 4 | [:api_token, { |
5 | 5 | :placeholder => "App ID", |
6 | 6 | :label => "App ID" |
... | ... | @@ -39,4 +39,4 @@ class NotificationServices::HoiioService < NotificationService |
39 | 39 | end |
40 | 40 | |
41 | 41 | end |
42 | -end | |
43 | 42 | \ No newline at end of file |
43 | +end | ... | ... |
... | ... | @@ -0,0 +1,32 @@ |
1 | +class NotificationServices::HubotService < NotificationService | |
2 | + Label = "hubot" | |
3 | + Fields += [ | |
4 | + [:api_token, { | |
5 | + :placeholder => 'http://hubot.example.org:8080/hubot/say', | |
6 | + :label => 'Hubot URL' | |
7 | + }], | |
8 | + [:room_id, { | |
9 | + :placeholder => '#dev', | |
10 | + :label => 'Room where Hubot should notify' | |
11 | + }] | |
12 | + ] | |
13 | + | |
14 | + def check_params | |
15 | + if Fields.detect {|f| self[f[0]].blank? } | |
16 | + errors.add :base, 'You must specify the URL of your hubot' | |
17 | + end | |
18 | + end | |
19 | + | |
20 | + def url | |
21 | + api_token | |
22 | + end | |
23 | + | |
24 | + def message_for_hubot(problem) | |
25 | + "[#{problem.app.name}][#{problem.environment}][#{problem.where}]: #{problem.error_class} #{problem_url(problem)}" | |
26 | + end | |
27 | + | |
28 | + def create_notification(problem) | |
29 | + HTTParty.post(url, :body => {:message => message_for_hubot(problem), :room => room_id}) | |
30 | + end | |
31 | +end | |
32 | + | ... | ... |
app/models/notification_services/pushover_service.rb
1 | 1 | class NotificationServices::PushoverService < NotificationService |
2 | 2 | Label = "pushover" |
3 | - Fields = [ | |
3 | + Fields += [ | |
4 | 4 | [:api_token, { |
5 | 5 | :placeholder => "User Key", |
6 | 6 | :label => "User Key" |
... | ... | @@ -29,4 +29,4 @@ class NotificationServices::PushoverService < NotificationService |
29 | 29 | notification.notify(api_token, "#{notification_description problem}", :priority => 1, :title => "Errbit Notification", :url => "http://#{Errbit::Config.host}/apps/#{problem.app.id.to_s}", :url_title => "Link to error") |
30 | 30 | |
31 | 31 | end |
32 | -end | |
33 | 32 | \ No newline at end of file |
33 | +end | ... | ... |
... | ... | @@ -0,0 +1,19 @@ |
1 | +class NotificationServices::WebhookService < NotificationService | |
2 | + Label = "webhook" | |
3 | + Fields = [ | |
4 | + [:api_token, { | |
5 | + :placeholder => 'URL to receive a POST request when an error occurs', | |
6 | + :label => 'URL' | |
7 | + }] | |
8 | + ] | |
9 | + | |
10 | + def check_params | |
11 | + if Fields.detect {|f| self[f[0]].blank? } | |
12 | + errors.add :base, 'You must specify the URL' | |
13 | + end | |
14 | + end | |
15 | + | |
16 | + def create_notification(problem) | |
17 | + HTTParty.post(api_token, :body => {:problem => problem.to_json}) | |
18 | + end | |
19 | +end | ... | ... |
app/models/problem.rb
... | ... | @@ -26,29 +26,39 @@ class Problem |
26 | 26 | field :hosts, :type => Hash, :default => {} |
27 | 27 | field :comments_count, :type => Integer, :default => 0 |
28 | 28 | |
29 | - index :app_id | |
30 | - index :app_name | |
31 | - index :message | |
32 | - index :last_notice_at | |
33 | - index :first_notice_at | |
34 | - index :last_deploy_at | |
35 | - index :resolved_at | |
36 | - index :notices_count | |
29 | + index :app_id => 1 | |
30 | + index :app_name => 1 | |
31 | + index :message => 1 | |
32 | + index :last_notice_at => 1 | |
33 | + index :first_notice_at => 1 | |
34 | + index :last_deploy_at => 1 | |
35 | + index :resolved_at => 1 | |
36 | + index :notices_count => 1 | |
37 | 37 | |
38 | 38 | belongs_to :app |
39 | 39 | has_many :errs, :inverse_of => :problem, :dependent => :destroy |
40 | 40 | has_many :comments, :inverse_of => :err, :dependent => :destroy |
41 | 41 | |
42 | + validates_presence_of :environment | |
43 | + | |
42 | 44 | before_create :cache_app_attributes |
43 | 45 | |
44 | 46 | scope :resolved, where(:resolved => true) |
45 | 47 | scope :unresolved, where(:resolved => false) |
46 | 48 | scope :ordered, order_by(:last_notice_at.desc) |
47 | 49 | scope :for_apps, lambda {|apps| where(:app_id.in => apps.all.map(&:id))} |
48 | - | |
50 | + | |
49 | 51 | validates_presence_of :last_notice_at, :first_notice_at |
50 | 52 | |
51 | 53 | |
54 | + def self.all_else_unresolved(fetch_all) | |
55 | + if fetch_all | |
56 | + all | |
57 | + else | |
58 | + where(:resolved => false) | |
59 | + end | |
60 | + end | |
61 | + | |
52 | 62 | def self.in_env(env) |
53 | 63 | env.present? ? where(:environment => env) : scoped |
54 | 64 | end |
... | ... | @@ -75,15 +85,7 @@ class Problem |
75 | 85 | |
76 | 86 | |
77 | 87 | def self.merge!(*problems) |
78 | - problems = problems.flatten.uniq | |
79 | - merged_problem = problems.shift | |
80 | - problems.each do |problem| | |
81 | - merged_problem.errs.concat Err.where(:problem_id => problem.id) | |
82 | - problem.errs(true) # reload problem.errs (should be empty) before problem.destroy | |
83 | - problem.destroy | |
84 | - end | |
85 | - merged_problem.reset_cached_attributes | |
86 | - merged_problem | |
88 | + ProblemMerge.new(problems).merge | |
87 | 89 | end |
88 | 90 | |
89 | 91 | def merged? |
... | ... | @@ -91,11 +93,12 @@ class Problem |
91 | 93 | end |
92 | 94 | |
93 | 95 | def unmerge! |
96 | + attrs = {:error_class => error_class, :environment => environment} | |
94 | 97 | problem_errs = errs.to_a |
95 | 98 | problem_errs.shift |
96 | 99 | [self] + problem_errs.map(&:id).map do |err_id| |
97 | 100 | err = Err.find(err_id) |
98 | - app.problems.create.tap do |new_problem| | |
101 | + app.problems.create(attrs).tap do |new_problem| | |
99 | 102 | err.update_attribute(:problem_id, new_problem.id) |
100 | 103 | new_problem.reset_cached_attributes |
101 | 104 | end |
... | ... | @@ -113,16 +116,14 @@ class Problem |
113 | 116 | else raise("\"#{sort}\" is not a recognized sort") |
114 | 117 | end |
115 | 118 | end |
116 | - | |
119 | + | |
117 | 120 | def self.in_date_range(date_range) |
118 | 121 | where(:first_notice_at.lte => date_range.end).where("$or" => [{:resolved_at => nil}, {:resolved_at.gte => date_range.begin}]) |
119 | 122 | end |
120 | 123 | |
121 | 124 | |
122 | 125 | def reset_cached_attributes |
123 | - update_attribute(:notices_count, notices.count) | |
124 | - cache_app_attributes | |
125 | - cache_notice_attributes | |
126 | + ProblemUpdaterCache.new(self).update | |
126 | 127 | end |
127 | 128 | |
128 | 129 | def cache_app_attributes |
... | ... | @@ -131,32 +132,12 @@ class Problem |
131 | 132 | self.last_deploy_at = if (last_deploy = app.deploys.where(:environment => self.environment).last) |
132 | 133 | last_deploy.created_at.utc |
133 | 134 | end |
134 | - collection.update({'_id' => self.id}, | |
135 | - {'$set' => {'app_name' => self.app_name, | |
135 | + collection.find('_id' => self.id) | |
136 | + .update({'$set' => {'app_name' => self.app_name, | |
136 | 137 | 'last_deploy_at' => self.last_deploy_at.try(:utc)}}) |
137 | 138 | end |
138 | 139 | end |
139 | 140 | |
140 | - def cache_notice_attributes(notice=nil) | |
141 | - first_notice = notices.order_by([:created_at, :asc]).first | |
142 | - last_notice = notices.order_by([:created_at, :asc]).last | |
143 | - notice ||= first_notice | |
144 | - | |
145 | - attrs = {} | |
146 | - attrs[:first_notice_at] = first_notice.created_at if first_notice | |
147 | - attrs[:last_notice_at] = last_notice.created_at if last_notice | |
148 | - attrs.merge!( | |
149 | - :message => notice.message, | |
150 | - :environment => notice.environment_name, | |
151 | - :error_class => notice.error_class, | |
152 | - :where => notice.where, | |
153 | - :messages => attribute_count_increase(:messages, notice.message), | |
154 | - :hosts => attribute_count_increase(:hosts, notice.host), | |
155 | - :user_agents => attribute_count_increase(:user_agents, notice.user_agent_string) | |
156 | - ) if notice | |
157 | - update_attributes!(attrs) | |
158 | - end | |
159 | - | |
160 | 141 | def remove_cached_notice_attributes(notice) |
161 | 142 | update_attributes!( |
162 | 143 | :messages => attribute_count_descrease(:messages, notice.message), |
... | ... | @@ -171,16 +152,17 @@ class Problem |
171 | 152 | (app.issue_tracker_configured? && app.issue_tracker.label) || nil |
172 | 153 | end |
173 | 154 | |
155 | + def self.search(value) | |
156 | + any_of( | |
157 | + {:error_class => /#{value}/i}, | |
158 | + {:where => /#{value}/i}, | |
159 | + {:message => /#{value}/i}, | |
160 | + {:app_name => /#{value}/i}, | |
161 | + {:environment => /#{value}/i} | |
162 | + ) | |
163 | + end | |
164 | + | |
174 | 165 | private |
175 | - def attribute_count_increase(name, value) | |
176 | - counter, index = send(name), attribute_index(value) | |
177 | - if counter[index].nil? | |
178 | - counter[index] = {'value' => value, 'count' => 1} | |
179 | - else | |
180 | - counter[index]['count'] += 1 | |
181 | - end | |
182 | - counter | |
183 | - end | |
184 | 166 | |
185 | 167 | def attribute_count_descrease(name, value) |
186 | 168 | counter, index = send(name), attribute_index(value) |
... | ... | @@ -195,6 +177,5 @@ class Problem |
195 | 177 | def attribute_index(value) |
196 | 178 | Digest::MD5.hexdigest(value.to_s) |
197 | 179 | end |
198 | - | |
199 | 180 | end |
200 | 181 | ... | ... |
app/models/user.rb
... | ... | @@ -13,14 +13,33 @@ class User |
13 | 13 | field :per_page, :type => Fixnum, :default => PER_PAGE |
14 | 14 | field :time_zone, :default => "UTC" |
15 | 15 | |
16 | - after_destroy :destroy_watchers | |
16 | + ## Devise field | |
17 | + ### Database Authenticatable | |
18 | + field :encrypted_password, :type => String | |
19 | + | |
20 | + ### Recoverable | |
21 | + field :reset_password_token, :type => String | |
22 | + field :reset_password_sent_at, :type => Time | |
23 | + | |
24 | + ### Rememberable | |
25 | + field :remember_created_at, :type => Time | |
26 | + | |
27 | + ### Trackable | |
28 | + field :sign_in_count, :type => Integer | |
29 | + field :current_sign_in_at, :type => Time | |
30 | + field :last_sign_in_at, :type => Time | |
31 | + field :current_sign_in_ip, :type => String | |
32 | + field :last_sign_in_ip, :type => String | |
33 | + | |
34 | + ### Token_authenticatable | |
35 | + field :authentication_token, :type => String | |
36 | + | |
37 | + | |
17 | 38 | before_save :ensure_authentication_token |
18 | 39 | |
19 | 40 | validates_presence_of :name |
20 | 41 | validates_uniqueness_of :github_login, :allow_nil => true |
21 | 42 | |
22 | - attr_protected :admin | |
23 | - | |
24 | 43 | has_many :apps, :foreign_key => 'watchers.user_id' |
25 | 44 | |
26 | 45 | if Errbit::Config.user_has_username |
... | ... | @@ -52,10 +71,12 @@ class User |
52 | 71 | github_account? && Errbit::Config.github_access_scope.include?('repo') |
53 | 72 | end |
54 | 73 | |
55 | - protected | |
56 | - | |
57 | - def destroy_watchers | |
58 | - watchers.each(&:destroy) | |
74 | + def github_login=(login) | |
75 | + if login.is_a?(String) && login.strip.empty? | |
76 | + login = nil | |
59 | 77 | end |
78 | + self[:github_login] = login | |
79 | + end | |
80 | + | |
60 | 81 | end |
61 | 82 | ... | ... |
app/views/apps/_fields.html.haml
1 | -= errors_for @app | |
1 | += errors_for app | |
2 | 2 | |
3 | 3 | %div.required |
4 | 4 | = f.label :name |
... | ... | @@ -13,6 +13,10 @@ |
13 | 13 | %div |
14 | 14 | = f.label :bitbucket_repo |
15 | 15 | = f.text_field :bitbucket_repo, :placeholder => "errbit/errbit from https://bitbucket.org/errbit/errbit" |
16 | +%div | |
17 | + = f.label :asset_host | |
18 | + %em Used to generate links for JavaScript errors | |
19 | + = f.text_field :asset_host, :placeholder => "e.g. https://assets.example.com" | |
16 | 20 | |
17 | 21 | %fieldset |
18 | 22 | %legend Notifications | ... | ... |
app/views/apps/_service_notification_fields.html.haml
... | ... | @@ -17,7 +17,8 @@ |
17 | 17 | - notification_service::Fields.each do |field, field_info| |
18 | 18 | = w.label field, field_info[:label] || field.to_s.titleize |
19 | 19 | - field_type = field == :password ? :password_field : :text_field |
20 | - = w.send field_type, field, :placeholder => field_info[:placeholder], :value => w.object.send(field) | |
20 | + - value = field == :notify_at_notices ? w.object.notify_at_notices.join(", ") : w.object.send(field) | |
21 | + = w.send field_type, field, :placeholder => field_info[:placeholder], :value => value | |
21 | 22 | |
22 | 23 | .image_preloader |
23 | 24 | - (NotificationService.subclasses.map{|t| t.label } << 'none').each do |notification_service| | ... | ... |
app/views/apps/edit.html.haml
1 | 1 | - content_for :title, 'Edit App' |
2 | 2 | - content_for :action_bar do |
3 | 3 | = link_to_copy_attributes_from_other_app |
4 | - = link_to('cancel', app_path(@app), :class => 'button') | |
4 | + = link_to 'destroy application', app_path(app), :method => :delete, :data => { :confirm => 'Seriously?' }, :class => 'button' | |
5 | + = link_to('cancel', app_path(app), :class => 'button') | |
5 | 6 | |
6 | -= form_for @app do |f| | |
7 | += form_for app do |f| | |
7 | 8 | |
8 | 9 | = render 'fields', :f => f |
9 | 10 | ... | ... |
app/views/apps/index.html.haml
1 | -- content_for :title, 'Apps' | |
1 | +- content_for :title, t('.title') | |
2 | 2 | - content_for :action_bar do |
3 | - %span= link_to('Add a New App', new_app_path, :class => 'add') if current_user.admin? | |
3 | + %span= link_to(t('.new_app'), new_app_path, :class => 'add') if current_user.admin? | |
4 | 4 | |
5 | 5 | %table.apps |
6 | 6 | %thead |
7 | 7 | %tr |
8 | - %th Name | |
8 | + %th= t('.name') | |
9 | 9 | - if any_github_repos? || any_bitbucket_repos? |
10 | - %th Repository | |
10 | + %th= t('.repository') | |
11 | 11 | - if any_notification_services? |
12 | - %th Notification Service | |
12 | + %th= t('.notify') | |
13 | 13 | - if any_issue_trackers? |
14 | - %th Tracker | |
14 | + %th= t('.tracker') | |
15 | 15 | - if any_deploys? |
16 | - %th Last Deploy | |
17 | - %th Errors | |
16 | + %th= t('.last_deploy') | |
17 | + %th=t('.errors') | |
18 | 18 | %tbody |
19 | - - @apps.each do |app| | |
19 | + - apps.each do |app| | |
20 | 20 | %tr |
21 | 21 | %td.name= link_to app.name, app_path(app) |
22 | 22 | - if any_github_repos? or any_bitbucket_repos? |
... | ... | @@ -50,10 +50,10 @@ |
50 | 50 | - if app.problem_count > 0 |
51 | 51 | - unresolved = app.unresolved_count |
52 | 52 | = link_to unresolved, app_path(app), :class => (unresolved == 0 ? "resolved" : nil) |
53 | - - if @apps.none? | |
53 | + - if apps.none? | |
54 | 54 | %tr |
55 | 55 | %td{:colspan => 3} |
56 | 56 | %em |
57 | - No apps here. | |
58 | - = link_to 'Click here to create your first one', new_app_path | |
57 | + = t('.no_apps') | |
58 | + = link_to t('.click_to_create'), new_app_path | |
59 | 59 | ... | ... |
app/views/apps/new.html.haml
app/views/apps/show.atom.builder
app/views/apps/show.html.haml
1 | -- content_for :title, @app.name | |
1 | +- content_for :title, app.name | |
2 | 2 | - content_for :head do |
3 | - = auto_discovery_link_tag :atom, app_path(@app, User.token_authentication_key => current_user.authentication_token, :format => "atom"), :title => "Errbit notices for #{@app.name} at #{request.host}" | |
3 | + = auto_discovery_link_tag :atom, app_path(app, User.token_authentication_key => current_user.authentication_token, :format => "atom"), :title => "Errbit notices for #{app.name} at #{request.host}" | |
4 | 4 | - content_for :meta do |
5 | 5 | %strong Errors Caught: |
6 | - = @app.problems.count | |
6 | + = app.problems.count | |
7 | 7 | %strong Deploy Count: |
8 | - = @app.deploys.count | |
8 | + = app.deploys.count | |
9 | 9 | %strong API Key: |
10 | - = @app.api_key | |
10 | + = app.api_key | |
11 | 11 | - content_for :action_bar do |
12 | 12 | - if current_user.admin? |
13 | - = link_to 'edit', edit_app_path(@app), :class => 'button' | |
14 | - = link_to 'destroy', app_path(@app), :method => :delete, :data => { :confirm => 'Seriously?' }, :class => 'button' | |
15 | - - if @all_errs | |
16 | - = link_to 'unresolved errs', app_path(@app), :class => 'button' | |
13 | + = link_to 'edit', edit_app_path(app), :class => 'button' | |
14 | + - if all_errs | |
15 | + = link_to 'unresolved errs', app_path(app), :class => 'button' | |
17 | 16 | - else |
18 | - = link_to 'all errs', app_path(@app, :all_errs => true), :class => 'button' | |
17 | + = link_to 'all errs', app_path(app, :all_errs => true), :class => 'button' | |
19 | 18 | = link_to 'unwatch', app_watcher_path({:app_id => @app, :id => current_user.id}), :method => :delete, :class => 'button', :confirm => 'Are you sure?' |
19 | + | |
20 | 20 | %h3#watchers_toggle |
21 | 21 | Watchers |
22 | 22 | %span.click_span (show/hide) |
23 | 23 | #watchers_div |
24 | - - if @app.notify_all_users | |
24 | + - if app.notify_all_users | |
25 | 25 | %table.watchers |
26 | 26 | %thead |
27 | 27 | %tr |
... | ... | @@ -32,15 +32,15 @@ |
32 | 32 | %tr |
33 | 33 | %th User or Email |
34 | 34 | %tbody |
35 | - - @app.watchers.each do |watcher| | |
35 | + - app.watchers.each do |watcher| | |
36 | 36 | %tr |
37 | 37 | %td= watcher.label |
38 | - - if @app.watchers.none? | |
38 | + - if app.watchers.none? | |
39 | 39 | %tr |
40 | 40 | %td |
41 | 41 | %em Sadly, no one is watching this app |
42 | 42 | |
43 | -- if @app.github_repo? | |
43 | +- if app.github_repo? | |
44 | 44 | %h3#repository_toggle |
45 | 45 | Repository |
46 | 46 | %span.click_span (show/hide) |
... | ... | @@ -51,13 +51,13 @@ |
51 | 51 | %th GitHub Repo |
52 | 52 | %tbody |
53 | 53 | %tr |
54 | - %td= link_to(@app.github_repo, @app.github_url, :target => '_blank') | |
54 | + %td= link_to(app.github_repo, app.github_url, :target => '_blank') | |
55 | 55 | |
56 | 56 | %h3#deploys_toggle |
57 | 57 | Latest Deploys |
58 | 58 | %span.click_span (show/hide) |
59 | 59 | #deploys_div |
60 | - - if @deploys.any? | |
60 | + - if deploys.any? | |
61 | 61 | %table.deploys |
62 | 62 | %thead |
63 | 63 | %tr |
... | ... | @@ -69,7 +69,7 @@ |
69 | 69 | %th Revision |
70 | 70 | |
71 | 71 | %tbody |
72 | - - @deploys.each do |deploy| | |
72 | + - deploys.each do |deploy| | |
73 | 73 | %tr |
74 | 74 | %td.when #{deploy.created_at.to_s(:micro)} |
75 | 75 | %td.environment #{deploy.environment} |
... | ... | @@ -77,14 +77,20 @@ |
77 | 77 | %td.message #{deploy.message} |
78 | 78 | %td.repository #{deploy.repository} |
79 | 79 | %td.revision #{deploy.short_revision} |
80 | - = link_to "All Deploys (#{@app.deploys.count})", app_deploys_path(@app), :class => 'button' | |
80 | + = link_to "All Deploys (#{app.deploys.count})", app_deploys_path(app), :class => 'button' | |
81 | 81 | - else |
82 | 82 | %h3 No deploys |
83 | 83 | |
84 | -- if @app.problems.any? | |
84 | +- if app.problems.any? | |
85 | 85 | %h3.clear Errors |
86 | - = render 'problems/table', :problems => @problems | |
86 | + %section | |
87 | + = form_tag search_problems_path(:all_errs => all_errs, :app_id => app.id), :method => :get, :remote => true do | |
88 | + = text_field_tag :search, params[:search], :placeholder => 'Search for issues' | |
89 | + %br | |
90 | + %section | |
91 | + .problem_table{:id => 'problem_table'} | |
92 | + = render 'problems/table', :problems => problems | |
87 | 93 | - else |
88 | 94 | %h3.clear No errs have been caught yet, make sure you setup your app |
89 | - = render 'configuration_instructions', :app => @app | |
95 | + = render 'configuration_instructions', :app => app | |
90 | 96 | ... | ... |
app/views/devise/sessions/new.html.haml
... | ... | @@ -9,7 +9,7 @@ |
9 | 9 | = form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| |
10 | 10 | .required |
11 | 11 | = f.label auth_key |
12 | - = f.text_field auth_key, :tabindex => 1 | |
12 | + = f.text_field auth_key, :type => (Errbit::Config.user_has_username ? 'text' : 'email'), :tabindex => 1 | |
13 | 13 | |
14 | 14 | .required |
15 | 15 | = link_to 'forget it?', new_password_path(resource_name), :class => 'float-right', :id => "forgot_password" | ... | ... |
app/views/issue_trackers/gitlab_body.txt.erb
1 | -[See this exception on Errbit](<%= app_problem_url problem.app, problem %> "See this exception on Errbit") | |
2 | 1 | <% if notice = problem.notices.first %> |
3 | -# <%= notice.message %> # | |
4 | -## Summary ## | |
5 | -<% if notice.request['url'].present? %> | |
6 | - ### URL ### | |
7 | - [<%= notice.request['url'] %>](<%= notice.request['url'] %>)" | |
8 | -<% end %> | |
9 | -### Where ### | |
10 | -<%= notice.where %> | |
11 | - | |
12 | -### Occured ### | |
13 | -<%= notice.created_at.to_s(:micro) %> | |
14 | - | |
15 | -### Similar ### | |
16 | -<%= (notice.problem.notices_count - 1).to_s %> | |
17 | - | |
18 | 2 | ## Params ## |
19 | 3 | ``` |
20 | 4 | <%= pretty_hash(notice.params) %> | ... | ... |
... | ... | @@ -0,0 +1,17 @@ |
1 | +[See this exception on Errbit](<%= app_problem_url problem.app, problem %> "See this exception on Errbit") | |
2 | +<% if notice = problem.notices.first %> | |
3 | +# <%= notice.message %> # | |
4 | +## Summary ## | |
5 | +<% if notice.request['url'].present? %> | |
6 | + ### URL ### | |
7 | + [<%= notice.request['url'] %>](<%= notice.request['url'] %>)" | |
8 | +<% end %> | |
9 | +### Where ### | |
10 | +<%= notice.where %> | |
11 | + | |
12 | +### Occured ### | |
13 | +<%= notice.created_at.to_s(:micro) %> | |
14 | + | |
15 | +### Similar ### | |
16 | +<%= (notice.problem.notices_count - 1).to_s %> | |
17 | +<% end %> | |
0 | 18 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,17 @@ |
1 | +<% if notice = problem.notices.first %> | |
2 | +h2. Summary | |
3 | +<% if notice.request['url'].present? %> | |
4 | +h3. URL | |
5 | + | |
6 | +"<%= notice.request['url'] %>":<%= notice.request['url'] %> | |
7 | +<% end %> | |
8 | +h3. Where | |
9 | + | |
10 | +<%= notice.where %> | |
11 | + | |
12 | +h3. When | |
13 | + | |
14 | +<%= notice.created_at.to_s(:micro) %> | |
15 | + | |
16 | +"More Details on Errbit":<%= app_problem_url problem.app, problem %> | |
17 | +<% end %> | |
0 | 18 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,17 @@ |
1 | +<% if notice = problem.notices.first %> | |
2 | +h2. Summary | |
3 | +<% if notice.request['url'].present? %> | |
4 | +h3. URL | |
5 | + | |
6 | +"<%= notice.request['url'] %>":<%= notice.request['url'] %> | |
7 | +<% end %> | |
8 | +h3. Where | |
9 | + | |
10 | +<%= notice.where %> | |
11 | + | |
12 | +h3. When | |
13 | + | |
14 | +<%= notice.created_at.to_s(:micro) %> | |
15 | + | |
16 | +"More Details on Errbit":<%= app_problem_url problem.app, problem %> | |
17 | +<% end %> | |
0 | 18 | \ No newline at end of file | ... | ... |
app/views/kaminari/notices/_paginator.html.haml
... | ... | @@ -6,9 +6,9 @@ |
6 | 6 | -# remote: data-remote |
7 | 7 | -# paginator: the paginator that renders the pagination tags inside |
8 | 8 | = paginator.render do |
9 | - .notice-pagination< | |
9 | + .notice-pagination | |
10 | 10 | = next_page_tag |
11 | - | | |
11 | + | | |
12 | 12 | = prev_page_tag |
13 | 13 | .notice-pagination-loader= image_tag 'loader.gif' |
14 | -viewing occurrence #{page_count_from_end(current_page, total_pages)} of #{total_pages} | |
14 | +%div viewing occurrence #{page_count_from_end(current_page, total_pages)} of #{total_pages} | ... | ... |
app/views/layouts/application.html.haml
... | ... | @@ -2,7 +2,8 @@ |
2 | 2 | %html |
3 | 3 | %head |
4 | 4 | %title |
5 | - Errbit — | |
5 | + = t('.title') | |
6 | + — | |
6 | 7 | = yield(:page_title).present? ? yield(:page_title) : yield(:title) |
7 | 8 | %meta{ :content => "text/html; charset=utf-8", "http-equiv" => "content-type" }/ |
8 | 9 | = favicon_link_tag |
... | ... | @@ -14,7 +15,7 @@ |
14 | 15 | %body{:id => controller.controller_name, :class => controller.action_name} |
15 | 16 | #header |
16 | 17 | %div |
17 | - = link_to 'Errbit', root_path, :id => 'site-name' | |
18 | + = link_to t('.errbit'), root_path, :id => 'site-name' | |
18 | 19 | = render 'shared/navigation' if current_user |
19 | 20 | = render 'shared/session' |
20 | 21 | #content-wrapper |
... | ... | @@ -30,5 +31,5 @@ |
30 | 31 | - if content_for?(:comments) |
31 | 32 | #content-comments |
32 | 33 | = yield :comments |
33 | - #footer Powered by #{link_to 'Errbit', 'http://github.com/errbit/errbit', :target => '_blank'}: the open source error catcher. | |
34 | + #footer= t('.powered_html', :link => link_to(t('.errbit'), 'http://github.com/errbit/errbit', :target => '_blank')) | |
34 | 35 | = yield :scripts | ... | ... |
... | ... | @@ -0,0 +1,50 @@ |
1 | +%tr | |
2 | + %td.section | |
3 | + %table(cellpadding="0" cellspacing="0" border="0" align="left") | |
4 | + %tbody | |
5 | + %tr | |
6 | + %td.content(valign="top") | |
7 | + %div | |
8 | + %p | |
9 | + = @user.name | |
10 | + has just commented on an error that occurred in | |
11 | + = link_to(@app.name, app_url(@app), :class => "bold") << "," | |
12 | + on the | |
13 | + %span.bold= @problem.environment | |
14 | + environment. | |
15 | + %br | |
16 | + This err has occurred #{pluralize @problem.notices_count, 'time'}. | |
17 | + %p | |
18 | + = link_to("Click here to view the error and add a comment on Errbit", app_problem_url(@app, @problem), :class => "bold") << "." | |
19 | + | |
20 | +%tr | |
21 | + %td.section | |
22 | + %table(cellpadding="0" cellspacing="0" border="0" align="left") | |
23 | + %tbody | |
24 | + %tr | |
25 | + %td.content(valign="top") | |
26 | + %div | |
27 | + %p.heading COMMENT: | |
28 | + %br | |
29 | + %p= @comment.body.to_s.gsub("\n", "<br/>").html_safe | |
30 | + | |
31 | +%tr | |
32 | + %td.section | |
33 | + %table(cellpadding="0" cellspacing="0" border="0" align="left") | |
34 | + %tbody | |
35 | + %tr | |
36 | + %td.content(valign="top") | |
37 | + %div | |
38 | + %p.heading ERROR MESSAGE: | |
39 | + %p= @problem.message | |
40 | + %p.heading WHERE: | |
41 | + %p.monospace | |
42 | + = @problem.where | |
43 | + %p.heading URL: | |
44 | + %p.monospace | |
45 | + - if @notice.request['url'].present? | |
46 | + = link_to @notice.request['url'], @notice.request['url'] | |
47 | + %p.heading BROWSER: | |
48 | + %p.monospace | |
49 | + = user_agent_graph(@problem) | |
50 | + %br | ... | ... |
... | ... | @@ -0,0 +1,36 @@ |
1 | +<%= @user.name %> has just commented on an error that occurred in <%= @notice.environment_name %>: <%= raw(@notice.message) %> | |
2 | + | |
3 | +This err has occurred <%= pluralize @notice.problem.notices_count, 'time' %>. You should really look into it here: | |
4 | + | |
5 | + <%= app_problem_url(@app, @notice.problem) %> | |
6 | + | |
7 | + | |
8 | +COMMENT: | |
9 | + | |
10 | +<%= @comment.body %> | |
11 | + | |
12 | + | |
13 | +----------------------------------------------- | |
14 | + | |
15 | +ERROR MESSAGE: | |
16 | + | |
17 | +<%= raw(@notice.message) %> | |
18 | + | |
19 | + | |
20 | +WHERE: | |
21 | + | |
22 | +<%= @notice.where %> | |
23 | + | |
24 | +<% @notice.in_app_backtrace_lines.each do |line| %> | |
25 | + <%= line %> | |
26 | +<% end %> | |
27 | + | |
28 | + | |
29 | +URL: | |
30 | + | |
31 | +<%= @notice.request['url'] %> | |
32 | + | |
33 | + | |
34 | +BROWSER: | |
35 | + | |
36 | +<%= @notice.user_agent_string %> | ... | ... |