提交 cccd269d 编写于 作者: K Kamil Trzciński

Merge branch 'ci-and-ce-sitting-in-a-tree-k-i-s-s-i-n-g' into 'master'

Merge CI into CE

First step of #2164.

- [x] Merge latest CE master
- [x] Make application start
- [x] Re-use gitlab sessions (remove CI oauth part)
- [x] Get rid of gitlab_ci.yml config
- [x] Make tests start
- [x] Make most CI features works
- [x] Make tests green
- [x] Write migration documentation
- [x] Add CI builds to CE backup

See merge request !1204
......@@ -20,12 +20,13 @@ backups/*
config/aws.yml
config/database.yml
config/gitlab.yml
config/initializers/omniauth.rb
config/gitlab_ci.yml
config/initializers/rack_attack.rb
config/initializers/smtp_settings.rb
config/resque.yml
config/unicorn.rb
config/mail_room.yml
config/secrets.yml
coverage/*
db/*.sqlite3
db/*.sqlite3-journal
......@@ -41,3 +42,4 @@ rails_best_practices_output.html
/tags
tmp/
vendor/bundle/*
builds/*
......@@ -998,7 +998,9 @@ AllCops:
- 'tmp/**/*'
- 'bin/**/*'
- 'lib/backup/**/*'
- 'lib/ci/backup/**/*'
- 'lib/tasks/**/*'
- 'lib/ci/migrate/**/*'
- 'lib/email_validator.rb'
- 'lib/gitlab/upgrader.rb'
- 'lib/gitlab/seeder.rb'
v7.14.0 (unreleased)
- Truncate commit messages after subject line in table
- Adjust CI config to support Docker executors
- Added Application Settings
- Randomize test database for CI tests
- Make YAML validation stricter
- Use avatars received from GitLab
- Refactor GitLab API usage to use either access_token or private_token depending on what was specified during login
- Allow to use access_token for API requests
- Fix project API listing returning empty list when first projects are not added to CI
- Allow to define variables from YAML
- Added support for CI skipped status
- Fix broken yaml error saving
- Add committed_at to commits to properly order last commit (the force push issue)
- Rename type(s) to stage(s)
- Fix navigation icons
- Add missing stage when doing retry
- Require variable keys to be not-empty and unique
- Fix variable saving issue
- Display variable saving errors in variables page not the project's
- Added Build Triggers API
v7.13.1
- Fix: user could steal specific runner
- Fix: don't send notifications for jobs with allow_failure set
- Fix invalid link to doc.gitlab.com
v7.13.0
- Fix inline edit runner-description
- Allow to specify image and services in yml that can be used with docker
- Fix: No runner notification can see managers only
- Fix service testing for slack
- Ability to cancel all builds in commit at once
- Disable colors in rake tasks automatically (if IO is not a TTY)
- Implemented "rake env:info". Rake task to receive system information
- Fix coverage calculation on commit page
- Enhance YAML validation
- Redirect back after authorization
- Change favicon
- Refactoring: Get rid of private_token usage in the frontend.
- Allow to specify allow_failure for job
- Build traces is stored in the file instead of database
- Make the builds path configurable
- Disable link to runner if it's not assigned to specific project
- Store all secrets in config/secrets.yml
- Encrypt variables
- Allow to specify flexible list of types in yaml
v7.12.2
- Revert: Runner without tag should pick builds without tag only
v7.12.1
- Runner without tag should pick builds without tag only
- Explicit error in the GitLab when commit not found.
- Fix: lint with relative subpath
- Update webhook example
- Improved Lint stability
- Add warning when .gitlab-ci.yml not found
- Improved validation for .gitlab-ci.yml
- Fix list of branches in only section
- Fix "Status Badge" button
v7.12.0
- Endless scroll on the dashboard
- Add notification if there are no runners
- Fix pagination on dashboard
- Remove ID column from runners list in the admin area
- Increase default timeout for builds to 60 minutes
- Using .gitlab-ci.yml file instead of jobs
- Link to the runner from the build page for admin user
- Ability to set secret variables for runner
- Dont retry build when push same commit in same ref twice
- Admin area: show amount of runners with last contact less than a minute ago
- Fix re-adding project with the same name but different gitlab_id
- Implementation of Lint (.gitlab-ci.yml validation tool)
- Updated rails to 4.1.11
- API fix: project create call
- Link to web-editor with .gitlab-ci.yml
- Updated examples in the documentation
v7.11.0
- Deploy Jobs API calls
- Projects search on dashboard page
- Improved runners page
- Running and Pending tabs on admin builds page
- Fix [ci skip] tag, so you can skip CI triggering now
- Add HipChat notifications
- Clean up project advanced settings.
- Add a GitLab project path parameter to the project API
- Remove projects IDs from dashboard
- UI fix: Remove page headers from the admin area
- Improve Email templates
- Add backup/restore utility
- Coordinator stores information(version, platform, revision, etc.) about runners.
- Fixed pagination on dashboard
- Public accessible build and commit pages of public projects
- Fix vulnerability in the API when MySQL is used
v7.10.1
- Fix failing migration when update to 7.10 from 7.8 and older versions
sidekiq_wirker_fix
- added sidekiq.yml
- integrated in script/background_jobs
v7.10.0
- Projects sorting by last commit date
- Add project search at runner page
- Fix GitLab and CI projects collision
- Events for admin
- Events per projects
- Search for runners in admin area
- UI improvements: created separated admin section, removed useless project show page
- Runners sorting in admin area (by id)
- Remove protected_attributes gem
- Skip commit creation if there is no appropriate job
v7.9.3
- Contains no changes
- Developers can cancel and retry jobs
v7.9.2
- [Security] Already existing projects should not be served by shared runners
- Ability to run deploy job without test jobs (every push will trigger deploy job)
v7.9.1
- [Security] Adding explicit is_shared parameter to runner
- [Security] By default new projects are not served by shared runners
v7.9.0
- Reset user session if token is invalid
- Runner delete api endpoint
- Fix bug about showing edit button on commit page if user does not have permissions
- Allow to pass description and tag list during Runner's registration
- Added api for project jobs
- Implementation of deploy jobs after all parallel jobs(tests).
- Add scroll up/down buttons for better mobile experience with large build traces
- Add runner last contact (Kamil Trzciński)
- Allow to pause runners - when paused runner will not receive any new build (Kamil Trzciński)
- Add brakeman (security scanner for Ruby on Rails)
- Changed a color of the canceled builds
- Fix of show the same commits in different branches
v7.8.2
- Fix the broken build failed email
- Notify only pusher instead of commiter
v7.8.0
- Fix OAuth login with GitLab installed in relative URL
- GitLab CI has same version as GitLab since now
- Allow to pass description and tag list during Runner's registration (Kamil Trzciński)
- Update documentation (API, Install, Update)
- Skip refs field supports for wildcard branch name (ex. feature/*)
- Migrate E-mail notification to Services menu (Kamil Trzciński)
- Added Slack notifications (Kamil Trzciński)
- Disable turbolink on links pointing out to GitLab server
- Add test coverage parsing example for pytest-cov
- Upgrade raindrops gem
v5.4.2
- Fix exposure of project token via build data
v5.4.1
- Fix 500 if on builds page if build has no job
- Truncate project token from build trace
- Allow users with access to project see build trace
v5.4.0 (Requires GitLab 7.7)
- Fixed 500 error for badge if build is pending
- Non-admin users can now register specific runners for their projects
- Project specific runners page which users can access
- Remove progress output from schedule_builds cron job
- Fix schedule_builds rake task
- Fix test webhook button
- Job can be branch specific or tag specific or both
- Shared runners builds projects which are not assigned to specific ones
- Job can be runner specific through tags
- Runner have tags
- Move job settings to separate page
- Add authorization level managing projects
- OAuth authentication via GitLab.
v5.3
- Remove annoying 'Done' message from schedule_builds cron job
- Fix a style issue with the navbar
- Skip CSRF check on the project's build page
- Fix showing wrong build script on admin projects page
- Add branch and commit message to build result emails
v5.2
- Improve performance by adding new indicies
- Separate Commit logic from Build logic in prep for Parallel Builds
- Parallel builds
- You can have multiple build scripts per project
v5.1
- Registration token and runner token are named differently
- Redirect to previous page after sign-in
- Dont show archived projects
- Add support for skip branches from build
- Add coverage parsing feature
- Update rails to 4.0.10
- Look for a REVISION file before running `git log`
- All builds page for admin
v5.0.1
- Update rails to 4.0.5
v5.0.0
- Set build timeout in minutes
- Web Hooks for builds
- Nprogress bar
- Remove extra spaces in build script
- Requires runner v5
* All script commands executed as one file
* Cancel button works correctly now
* Runner stability increased
* Timeout applies to build now instead of line of script
v4.3.0
- Refactor build js
- Redirect to build page with sha + bid if build id is not provided
- Update rails to 4.0.3
- Restyle project settings page
- Improve help page
- Replaced puma with unicorn
- Improved init.d script
- Add submodule init to default build script for new projects
v4.2.0
- Build duration chart
- Bootstrap 3 with responsive UI
- Improved init.d script
- Refactoring
- Changed http codes for POST /projects/:id/build action
- Turbolinks
v4.1.0
- Rails 4
- Click on build branch to see other builds for this branch
- Email notifications (Jeroen Knoops)
v4.0.0
- Shared runners (no need to add runner to every project)
- Admin area (only available for GitLab admins)
- Hide all runners management into admin area
- Use http cloning for builds instead of deploy keys
- Allow choose between git clone and git fetch when get code for build
- Make build timeout actually works
- Requires GitLab 6.3 or higher
- GitLab CI settings go to GitLab project via api on creation
v3.2.0
- Limit visibility of projects by gitlab authorized projects
- Use one page for both gitlab and gitlab-ci projects
v3.1.0
- Login with both username, email or LDAP credentials (if GitLab 6.0+)
- Retry build button functionality
- UI fixes for resolution 1366px and lower
- Fix gravatar ssl warning
v3.0.0
- Build running functionality extracted in gitlab-ci-runner
- Added API for runners and builds
- Redesigned application
- Added charts
- Use GitLab auth
- Add projects via UI with few clicks
v2.2.0
- replaced unicorn with puma
- replaced grit with rugged
- Runner.rb more transactional safe now
- updated rails to 3.2.13
- updated devise to 2.2
- fixed issue when build left in running status if exception triggered
- rescue build timeout correctly
- badge helper with markdown & html
- increased test coverage to 85%
v2.1.0
- Removed horizontal scroll for build trace
- new status badges
- better encode
- added several CI_* env variables
v2.0.0
- Replace resque with sidekiq
- Run only one build at time per project
- Added whenever for schedule jobs
v1.2.0
- Added Github web hook support
- Added build schedule
v1.1.0
- Added JSON response for builds status
- Compatible with GitLab v4.0.0
\ No newline at end of file
source "https://rubygems.org"
gem 'rails', '4.1.11'
def darwin_only(require_as)
RUBY_PLATFORM.include?('darwin') && require_as
end
def linux_only(require_as)
RUBY_PLATFORM.include?('linux') && require_as
end
gem 'rails', '4.1.12'
# Specify a sprockets version due to security issue
# See https://groups.google.com/forum/#!topic/rubyonrails-security/doAVp0YaTqY
......@@ -10,29 +18,29 @@ gem 'sprockets', '~> 2.12.3'
gem "default_value_for", "~> 3.0.0"
# Supported DBs
gem "mysql2", group: :mysql
gem "pg", group: :postgres
gem "mysql2", '~> 0.3.16', group: :mysql
gem "pg", '~> 0.18.2', group: :postgres
# Authentication libraries
gem "devise", '3.2.4'
gem "devise-async", '0.9.0'
gem "devise", '~> 3.2.4'
gem "devise-async", '~> 0.9.0'
gem 'omniauth', "~> 1.2.2"
gem 'omniauth-google-oauth2'
gem 'omniauth-twitter'
gem 'omniauth-github'
gem 'omniauth-shibboleth'
gem 'omniauth-kerberos', group: :kerberos
gem 'omniauth-gitlab'
gem 'omniauth-bitbucket'
gem 'omniauth-google-oauth2', '~> 0.2.5'
gem 'omniauth-twitter', '~> 1.0.1'
gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-shibboleth', '~> 1.1.1'
gem 'omniauth-kerberos', '~> 0.2.0', group: :kerberos
gem 'omniauth-gitlab', '~> 1.0.0'
gem 'omniauth-bitbucket', '~> 0.0.2'
gem 'omniauth-saml', '~> 1.4.0'
gem 'doorkeeper', '~> 2.1.3'
gem 'omniauth_crowd'
gem 'doorkeeper', '2.1.3'
gem "rack-oauth2", "~> 1.0.5"
# Two-factor authentication
gem 'devise-two-factor'
gem 'rqrcode-rails3'
gem 'attr_encrypted', '1.3.4'
gem 'devise-two-factor', '~> 1.0.1'
gem 'rqrcode-rails3', '~> 0.1.7'
gem 'attr_encrypted', '~> 1.3.4'
# Browser detection
gem "browser", '~> 1.0.0'
......@@ -44,7 +52,7 @@ gem "gitlab_git", '~> 7.2.15'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
# see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master
gem 'gitlab_omniauth-ldap', '1.2.1', require: "omniauth-ldap"
gem 'gitlab_omniauth-ldap', '~> 1.2.1', require: "omniauth-ldap"
# Git Wiki
gem 'gollum-lib', '~> 4.0.2'
......@@ -59,47 +67,47 @@ gem "gitlab-linguist", "~> 3.0.1", require: "linguist"
# API
gem "grape", "~> 0.6.1"
gem "grape-entity", "~> 0.4.2"
gem 'rack-cors', require: 'rack/cors'
gem 'rack-cors', '~> 0.2.9', require: 'rack/cors'
# Format dates and times
# based on human-friendly examples
gem "stamp"
gem "stamp", '~> 0.5.0'
# Enumeration fields
gem 'enumerize'
gem 'enumerize', '~> 0.7.0'
# Pagination
gem "kaminari", "~> 0.15.1"
# HAML
gem "haml-rails"
gem "haml-rails", '~> 0.5.3'
# Files attachments
gem "carrierwave"
gem "carrierwave", '~> 0.9.0'
# Drag and Drop UI
gem 'dropzonejs-rails'
gem 'dropzonejs-rails', '~> 0.7.1'
# for aws storage
gem "fog", "~> 1.25.0"
gem "unf"
gem "unf", '~> 0.1.4'
# Authorization
gem "six"
gem "six", '~> 0.2.0'
# Seed data
gem "seed-fu"
gem "seed-fu", '~> 2.3.5'
# Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.0'
gem 'task_list', '1.0.2', require: 'task_list/railtie'
gem 'github-markup'
gem 'task_list', '~> 1.0.2', require: 'task_list/railtie'
gem 'github-markup', '~> 1.3.1'
gem 'redcarpet', '~> 3.3.2'
gem 'RedCloth'
gem 'RedCloth', '~> 4.2.9'
gem 'rdoc', '~>3.6'
gem 'org-ruby', '= 0.9.12'
gem 'org-ruby', '~> 0.9.12'
gem 'creole', '~>0.3.6'
gem 'wikicloth', '=0.8.1'
gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 1.5.2'
# Diffs
......@@ -107,37 +115,38 @@ gem 'diffy', '~> 3.0.3'
# Application server
group :unicorn do
gem "unicorn", '~> 4.6.3'
gem 'unicorn-worker-killer'
gem "unicorn", '~> 4.8.2'
gem 'unicorn-worker-killer', '~> 0.4.2'
end
# State machine
gem "state_machine"
gem "state_machine", '~> 1.2.0'
# Issue tags
gem 'acts-as-taggable-on', '~> 3.4'
# Background jobs
gem 'slim'
gem 'sinatra', require: nil
gem 'sidekiq', '~> 3.3'
gem 'sidetiq', '0.6.3'
gem 'slim', '~> 2.0.2'
gem 'sinatra', '~> 1.4.4', require: nil
gem 'sidekiq', '3.3.0'
gem 'sidetiq', '~> 0.6.3'
# HTTP requests
gem "httparty"
gem "httparty", '~> 0.13.3'
# Colored output to console
gem "colored"
gem "colored", '~> 1.2'
gem "colorize", '~> 0.5.8'
# GitLab settings
gem 'settingslogic'
gem 'settingslogic', '~> 2.0.9'
# Misc
gem "foreman"
gem 'version_sorter'
gem 'version_sorter', '~> 2.0.0'
# Cache
gem "redis-rails"
gem "redis-rails", '~> 4.0.0'
# Campfire integration
gem 'tinder', '~> 1.9.2'
......@@ -176,69 +185,70 @@ gem "sanitize", '~> 2.0'
gem "rack-attack", '~> 4.3.0'
# Ace editor
gem 'ace-rails-ap'
gem 'ace-rails-ap', '~> 2.0.1'
# Keyboard shortcuts
gem 'mousetrap-rails'
gem 'mousetrap-rails', '~> 1.4.6'
# Detect and convert string character encoding
gem 'charlock_holmes'
gem 'charlock_holmes', '~> 0.6.9.4'
gem "sass-rails", '~> 4.0.5'
gem "coffee-rails"
gem "uglifier"
gem "coffee-rails", '~> 4.1.0'
gem "uglifier", '~> 2.3.2'
gem 'turbolinks', '~> 2.5.0'
gem 'jquery-turbolinks'
gem 'jquery-turbolinks', '~> 2.0.1'
gem 'addressable'
gem 'addressable', '~> 2.3.8'
gem 'bootstrap-sass', '~> 3.0'
gem 'font-awesome-rails', '~> 4.2'
gem 'gitlab_emoji', '~> 0.1'
gem 'gon', '~> 5.0.0'
gem 'jquery-atwho-rails', '~> 1.0.0'
gem 'jquery-rails', '3.1.3'
gem 'jquery-scrollto-rails'
gem 'jquery-ui-rails'
gem 'nprogress-rails'
gem 'jquery-rails', '~> 3.1.3'
gem 'jquery-scrollto-rails', '~> 1.4.3'
gem 'jquery-ui-rails', '~> 4.2.1'
gem 'nprogress-rails', '~> 0.1.2.3'
gem 'raphael-rails', '~> 2.1.2'
gem 'request_store'
gem 'request_store', '~> 1.2.0'
gem 'select2-rails', '~> 3.5.9'
gem 'virtus'
gem 'virtus', '~> 1.0.1'
group :development do
gem 'brakeman', require: false
gem "annotate", "~> 2.6.0.beta2"
gem "letter_opener"
gem 'quiet_assets', '~> 1.0.1'
gem 'rack-mini-profiler', require: false
gem "foreman"
gem 'brakeman', '3.0.1', require: false
gem "annotate", "~> 2.6.0"
gem "letter_opener", '~> 1.1.2'
gem 'quiet_assets', '~> 1.0.2'
gem 'rack-mini-profiler', '~> 0.9.0', require: false
gem 'rerun', '~> 0.10.0'
# Better errors handler
gem 'better_errors'
gem 'binding_of_caller'
gem 'better_errors', '~> 1.0.1'
gem 'binding_of_caller', '~> 0.7.2'
# Docs generator
gem "sdoc"
gem "sdoc", '~> 0.3.20'
# thin instead webrick
gem 'thin'
gem 'thin', '~> 1.6.1'
end
group :development, :test do
gem 'awesome_print'
gem 'byebug', platform: :mri
gem 'fuubar', '~> 2.0.0'
gem 'pry-rails'
gem 'coveralls', '~> 0.8.2', require: false
gem 'awesome_print', '~> 1.2.0'
gem 'fuubar', '~> 2.0.0'
gem 'database_cleaner', '~> 1.4.0'
gem 'factory_girl_rails'
gem 'factory_girl_rails', '~> 4.3.0'
gem 'rspec-rails', '~> 3.3.0'
gem 'rubocop', '0.28.0', require: false
gem 'spinach-rails'
gem 'spinach-rails', '~> 0.2.1'
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.3.0'
gem 'minitest', '~> 5.7.0'
# Generate Fake data
gem 'ffaker', '~> 2.0.0'
......@@ -248,20 +258,23 @@ group :development, :test do
gem 'poltergeist', '~> 1.6.0'
gem 'teaspoon', '~> 1.0.0'
gem 'teaspoon-jasmine'
gem 'teaspoon-jasmine', '~> 2.2.0'
gem 'spring', '~> 1.3.1'
gem 'spring-commands-rspec', '~> 1.0.0'
gem 'spring', '~> 1.3.6'
gem 'spring-commands-rspec', '~> 1.0.4'
gem 'spring-commands-spinach', '~> 1.0.0'
gem 'spring-commands-teaspoon', '~> 0.0.2'
gem 'rubocop', '~> 0.28.0', require: false
gem 'coveralls', '~> 0.8.2', require: false
gem 'simplecov', '~> 0.10.0', require: false
end
group :test do
gem 'simplecov', require: false
gem 'shoulda-matchers', '~> 2.8.0', require: false
gem 'email_spec', '~> 1.6.0'
gem 'webmock', '~> 1.21.0'
gem 'test_after_commit'
gem 'test_after_commit', '~> 0.2.2'
gem 'sham_rack'
end
......@@ -269,10 +282,32 @@ group :production do
gem "gitlab_meta", '7.0'
end
gem "newrelic_rpm"
gem "newrelic_rpm", '~> 3.9.4.245'
gem 'octokit', '3.7.0'
gem 'octokit', '~> 3.7.0'
gem "mail_room", "~> 0.4.2"
gem 'email_reply_parser'
gem 'email_reply_parser', '~> 0.5.8'
## CI
gem 'activerecord-deprecated_finders', '~> 1.0.3'
gem 'activerecord-session_store', '~> 0.1.0'
gem "nested_form", '~> 0.3.2'
# Scheduled
gem 'whenever', '~> 0.8.4', require: false
# OAuth
gem 'oauth2', '~> 1.0.0'
# Soft deletion
gem "paranoia", "~> 2.0"
group :development, :test do
gem 'guard-rspec', '~> 4.2.0'
gem 'rb-fsevent', require: darwin_only('rb-fsevent')
gem 'growl', require: darwin_only('growl')
gem 'rb-inotify', require: linux_only('rb-inotify')
end
此差异已折叠。
web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"}
worker: bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q common -q default
worker: bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default
# mail_room: bundle exec mail_room -q -c config/mail_room.yml
var Chart=function(s){function v(a,c,b){a=A((a-c.graphMin)/(c.steps*c.stepValue),1,0);return b*c.steps*a}function x(a,c,b,e){function h(){g+=f;var k=a.animation?A(d(g),null,0):1;e.clearRect(0,0,q,u);a.scaleOverlay?(b(k),c()):(c(),b(k));if(1>=g)D(h);else if("function"==typeof a.onAnimationComplete)a.onAnimationComplete()}var f=a.animation?1/A(a.animationSteps,Number.MAX_VALUE,1):1,d=B[a.animationEasing],g=a.animation?0:1;"function"!==typeof c&&(c=function(){});D(h)}function C(a,c,b,e,h,f){var d;a=
Math.floor(Math.log(e-h)/Math.LN10);h=Math.floor(h/(1*Math.pow(10,a)))*Math.pow(10,a);e=Math.ceil(e/(1*Math.pow(10,a)))*Math.pow(10,a)-h;a=Math.pow(10,a);for(d=Math.round(e/a);d<b||d>c;)a=d<b?a/2:2*a,d=Math.round(e/a);c=[];z(f,c,d,h,a);return{steps:d,stepValue:a,graphMin:h,labels:c}}function z(a,c,b,e,h){if(a)for(var f=1;f<b+1;f++)c.push(E(a,{value:(e+h*f).toFixed(0!=h%1?h.toString().split(".")[1].length:0)}))}function A(a,c,b){return!isNaN(parseFloat(c))&&isFinite(c)&&a>c?c:!isNaN(parseFloat(b))&&
isFinite(b)&&a<b?b:a}function y(a,c){var b={},e;for(e in a)b[e]=a[e];for(e in c)b[e]=c[e];return b}function E(a,c){var b=!/\W/.test(a)?F[a]=F[a]||E(document.getElementById(a).innerHTML):new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.replace(/[\r\t\n]/g," ").split("<%").join("\t").replace(/((^|%>)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');");return c?
b(c):b}var r=this,B={linear:function(a){return a},easeInQuad:function(a){return a*a},easeOutQuad:function(a){return-1*a*(a-2)},easeInOutQuad:function(a){return 1>(a/=0.5)?0.5*a*a:-0.5*(--a*(a-2)-1)},easeInCubic:function(a){return a*a*a},easeOutCubic:function(a){return 1*((a=a/1-1)*a*a+1)},easeInOutCubic:function(a){return 1>(a/=0.5)?0.5*a*a*a:0.5*((a-=2)*a*a+2)},easeInQuart:function(a){return a*a*a*a},easeOutQuart:function(a){return-1*((a=a/1-1)*a*a*a-1)},easeInOutQuart:function(a){return 1>(a/=0.5)?
0.5*a*a*a*a:-0.5*((a-=2)*a*a*a-2)},easeInQuint:function(a){return 1*(a/=1)*a*a*a*a},easeOutQuint:function(a){return 1*((a=a/1-1)*a*a*a*a+1)},easeInOutQuint:function(a){return 1>(a/=0.5)?0.5*a*a*a*a*a:0.5*((a-=2)*a*a*a*a+2)},easeInSine:function(a){return-1*Math.cos(a/1*(Math.PI/2))+1},easeOutSine:function(a){return 1*Math.sin(a/1*(Math.PI/2))},easeInOutSine:function(a){return-0.5*(Math.cos(Math.PI*a/1)-1)},easeInExpo:function(a){return 0==a?1:1*Math.pow(2,10*(a/1-1))},easeOutExpo:function(a){return 1==
a?1:1*(-Math.pow(2,-10*a/1)+1)},easeInOutExpo:function(a){return 0==a?0:1==a?1:1>(a/=0.5)?0.5*Math.pow(2,10*(a-1)):0.5*(-Math.pow(2,-10*--a)+2)},easeInCirc:function(a){return 1<=a?a:-1*(Math.sqrt(1-(a/=1)*a)-1)},easeOutCirc:function(a){return 1*Math.sqrt(1-(a=a/1-1)*a)},easeInOutCirc:function(a){return 1>(a/=0.5)?-0.5*(Math.sqrt(1-a*a)-1):0.5*(Math.sqrt(1-(a-=2)*a)+1)},easeInElastic:function(a){var c=1.70158,b=0,e=1;if(0==a)return 0;if(1==(a/=1))return 1;b||(b=0.3);e<Math.abs(1)?(e=1,c=b/4):c=b/(2*
Math.PI)*Math.asin(1/e);return-(e*Math.pow(2,10*(a-=1))*Math.sin((1*a-c)*2*Math.PI/b))},easeOutElastic:function(a){var c=1.70158,b=0,e=1;if(0==a)return 0;if(1==(a/=1))return 1;b||(b=0.3);e<Math.abs(1)?(e=1,c=b/4):c=b/(2*Math.PI)*Math.asin(1/e);return e*Math.pow(2,-10*a)*Math.sin((1*a-c)*2*Math.PI/b)+1},easeInOutElastic:function(a){var c=1.70158,b=0,e=1;if(0==a)return 0;if(2==(a/=0.5))return 1;b||(b=1*0.3*1.5);e<Math.abs(1)?(e=1,c=b/4):c=b/(2*Math.PI)*Math.asin(1/e);return 1>a?-0.5*e*Math.pow(2,10*
(a-=1))*Math.sin((1*a-c)*2*Math.PI/b):0.5*e*Math.pow(2,-10*(a-=1))*Math.sin((1*a-c)*2*Math.PI/b)+1},easeInBack:function(a){return 1*(a/=1)*a*(2.70158*a-1.70158)},easeOutBack:function(a){return 1*((a=a/1-1)*a*(2.70158*a+1.70158)+1)},easeInOutBack:function(a){var c=1.70158;return 1>(a/=0.5)?0.5*a*a*(((c*=1.525)+1)*a-c):0.5*((a-=2)*a*(((c*=1.525)+1)*a+c)+2)},easeInBounce:function(a){return 1-B.easeOutBounce(1-a)},easeOutBounce:function(a){return(a/=1)<1/2.75?1*7.5625*a*a:a<2/2.75?1*(7.5625*(a-=1.5/2.75)*
a+0.75):a<2.5/2.75?1*(7.5625*(a-=2.25/2.75)*a+0.9375):1*(7.5625*(a-=2.625/2.75)*a+0.984375)},easeInOutBounce:function(a){return 0.5>a?0.5*B.easeInBounce(2*a):0.5*B.easeOutBounce(2*a-1)+0.5}},q=s.canvas.width,u=s.canvas.height;window.devicePixelRatio&&(s.canvas.style.width=q+"px",s.canvas.style.height=u+"px",s.canvas.height=u*window.devicePixelRatio,s.canvas.width=q*window.devicePixelRatio,s.scale(window.devicePixelRatio,window.devicePixelRatio));this.PolarArea=function(a,c){r.PolarArea.defaults={scaleOverlay:!0,
scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleShowLine:!0,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",
animateRotate:!0,animateScale:!1,onAnimationComplete:null};var b=c?y(r.PolarArea.defaults,c):r.PolarArea.defaults;return new G(a,b,s)};this.Radar=function(a,c){r.Radar.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleShowLine:!0,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!1,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",
scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,angleShowLineOut:!0,angleLineColor:"rgba(0,0,0,.1)",angleLineWidth:1,pointLabelFontFamily:"'Arial'",pointLabelFontStyle:"normal",pointLabelFontSize:12,pointLabelFontColor:"#666",pointDot:!0,pointDotRadius:3,pointDotStrokeWidth:1,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null};var b=c?y(r.Radar.defaults,c):r.Radar.defaults;return new H(a,b,s)};this.Pie=function(a,
c){r.Pie.defaults={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,onAnimationComplete:null};var b=c?y(r.Pie.defaults,c):r.Pie.defaults;return new I(a,b,s)};this.Doughnut=function(a,c){r.Doughnut.defaults={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,percentageInnerCutout:50,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,
onAnimationComplete:null};var b=c?y(r.Doughnut.defaults,c):r.Doughnut.defaults;return new J(a,b,s)};this.Line=function(a,c){r.Line.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,bezierCurve:!0,
pointDot:!0,pointDotRadius:4,pointDotStrokeWidth:2,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null};var b=c?y(r.Line.defaults,c):r.Line.defaults;return new K(a,b,s)};this.Bar=function(a,c){r.Bar.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",
scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,barShowStroke:!0,barStrokeWidth:2,barValueSpacing:5,barDatasetSpacing:1,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null};var b=c?y(r.Bar.defaults,c):r.Bar.defaults;return new L(a,b,s)};var G=function(a,c,b){var e,h,f,d,g,k,j,l,m;g=Math.min.apply(Math,[q,u])/2;g-=Math.max.apply(Math,[0.5*c.scaleFontSize,0.5*c.scaleLineWidth]);
d=2*c.scaleFontSize;c.scaleShowLabelBackdrop&&(d+=2*c.scaleBackdropPaddingY,g-=1.5*c.scaleBackdropPaddingY);l=g;d=d?d:5;e=Number.MIN_VALUE;h=Number.MAX_VALUE;for(f=0;f<a.length;f++)a[f].value>e&&(e=a[f].value),a[f].value<h&&(h=a[f].value);f=Math.floor(l/(0.66*d));d=Math.floor(0.5*(l/d));m=c.scaleShowLabels?c.scaleLabel:null;c.scaleOverride?(j={steps:c.scaleSteps,stepValue:c.scaleStepWidth,graphMin:c.scaleStartValue,labels:[]},z(m,j.labels,j.steps,c.scaleStartValue,c.scaleStepWidth)):j=C(l,f,d,e,h,
m);k=g/j.steps;x(c,function(){for(var a=0;a<j.steps;a++)if(c.scaleShowLine&&(b.beginPath(),b.arc(q/2,u/2,k*(a+1),0,2*Math.PI,!0),b.strokeStyle=c.scaleLineColor,b.lineWidth=c.scaleLineWidth,b.stroke()),c.scaleShowLabels){b.textAlign="center";b.font=c.scaleFontStyle+" "+c.scaleFontSize+"px "+c.scaleFontFamily;var e=j.labels[a];if(c.scaleShowLabelBackdrop){var d=b.measureText(e).width;b.fillStyle=c.scaleBackdropColor;b.beginPath();b.rect(Math.round(q/2-d/2-c.scaleBackdropPaddingX),Math.round(u/2-k*(a+
1)-0.5*c.scaleFontSize-c.scaleBackdropPaddingY),Math.round(d+2*c.scaleBackdropPaddingX),Math.round(c.scaleFontSize+2*c.scaleBackdropPaddingY));b.fill()}b.textBaseline="middle";b.fillStyle=c.scaleFontColor;b.fillText(e,q/2,u/2-k*(a+1))}},function(e){var d=-Math.PI/2,g=2*Math.PI/a.length,f=1,h=1;c.animation&&(c.animateScale&&(f=e),c.animateRotate&&(h=e));for(e=0;e<a.length;e++)b.beginPath(),b.arc(q/2,u/2,f*v(a[e].value,j,k),d,d+h*g,!1),b.lineTo(q/2,u/2),b.closePath(),b.fillStyle=a[e].color,b.fill(),
c.segmentShowStroke&&(b.strokeStyle=c.segmentStrokeColor,b.lineWidth=c.segmentStrokeWidth,b.stroke()),d+=h*g},b)},H=function(a,c,b){var e,h,f,d,g,k,j,l,m;a.labels||(a.labels=[]);g=Math.min.apply(Math,[q,u])/2;d=2*c.scaleFontSize;for(e=l=0;e<a.labels.length;e++)b.font=c.pointLabelFontStyle+" "+c.pointLabelFontSize+"px "+c.pointLabelFontFamily,h=b.measureText(a.labels[e]).width,h>l&&(l=h);g-=Math.max.apply(Math,[l,1.5*(c.pointLabelFontSize/2)]);g-=c.pointLabelFontSize;l=g=A(g,null,0);d=d?d:5;e=Number.MIN_VALUE;
h=Number.MAX_VALUE;for(f=0;f<a.datasets.length;f++)for(m=0;m<a.datasets[f].data.length;m++)a.datasets[f].data[m]>e&&(e=a.datasets[f].data[m]),a.datasets[f].data[m]<h&&(h=a.datasets[f].data[m]);f=Math.floor(l/(0.66*d));d=Math.floor(0.5*(l/d));m=c.scaleShowLabels?c.scaleLabel:null;c.scaleOverride?(j={steps:c.scaleSteps,stepValue:c.scaleStepWidth,graphMin:c.scaleStartValue,labels:[]},z(m,j.labels,j.steps,c.scaleStartValue,c.scaleStepWidth)):j=C(l,f,d,e,h,m);k=g/j.steps;x(c,function(){var e=2*Math.PI/
a.datasets[0].data.length;b.save();b.translate(q/2,u/2);if(c.angleShowLineOut){b.strokeStyle=c.angleLineColor;b.lineWidth=c.angleLineWidth;for(var d=0;d<a.datasets[0].data.length;d++)b.rotate(e),b.beginPath(),b.moveTo(0,0),b.lineTo(0,-g),b.stroke()}for(d=0;d<j.steps;d++){b.beginPath();if(c.scaleShowLine){b.strokeStyle=c.scaleLineColor;b.lineWidth=c.scaleLineWidth;b.moveTo(0,-k*(d+1));for(var f=0;f<a.datasets[0].data.length;f++)b.rotate(e),b.lineTo(0,-k*(d+1));b.closePath();b.stroke()}c.scaleShowLabels&&
(b.textAlign="center",b.font=c.scaleFontStyle+" "+c.scaleFontSize+"px "+c.scaleFontFamily,b.textBaseline="middle",c.scaleShowLabelBackdrop&&(f=b.measureText(j.labels[d]).width,b.fillStyle=c.scaleBackdropColor,b.beginPath(),b.rect(Math.round(-f/2-c.scaleBackdropPaddingX),Math.round(-k*(d+1)-0.5*c.scaleFontSize-c.scaleBackdropPaddingY),Math.round(f+2*c.scaleBackdropPaddingX),Math.round(c.scaleFontSize+2*c.scaleBackdropPaddingY)),b.fill()),b.fillStyle=c.scaleFontColor,b.fillText(j.labels[d],0,-k*(d+
1)))}for(d=0;d<a.labels.length;d++){b.font=c.pointLabelFontStyle+" "+c.pointLabelFontSize+"px "+c.pointLabelFontFamily;b.fillStyle=c.pointLabelFontColor;var f=Math.sin(e*d)*(g+c.pointLabelFontSize),h=Math.cos(e*d)*(g+c.pointLabelFontSize);b.textAlign=e*d==Math.PI||0==e*d?"center":e*d>Math.PI?"right":"left";b.textBaseline="middle";b.fillText(a.labels[d],f,-h)}b.restore()},function(d){var e=2*Math.PI/a.datasets[0].data.length;b.save();b.translate(q/2,u/2);for(var g=0;g<a.datasets.length;g++){b.beginPath();
b.moveTo(0,d*-1*v(a.datasets[g].data[0],j,k));for(var f=1;f<a.datasets[g].data.length;f++)b.rotate(e),b.lineTo(0,d*-1*v(a.datasets[g].data[f],j,k));b.closePath();b.fillStyle=a.datasets[g].fillColor;b.strokeStyle=a.datasets[g].strokeColor;b.lineWidth=c.datasetStrokeWidth;b.fill();b.stroke();if(c.pointDot){b.fillStyle=a.datasets[g].pointColor;b.strokeStyle=a.datasets[g].pointStrokeColor;b.lineWidth=c.pointDotStrokeWidth;for(f=0;f<a.datasets[g].data.length;f++)b.rotate(e),b.beginPath(),b.arc(0,d*-1*
v(a.datasets[g].data[f],j,k),c.pointDotRadius,2*Math.PI,!1),b.fill(),b.stroke()}b.rotate(e)}b.restore()},b)},I=function(a,c,b){for(var e=0,h=Math.min.apply(Math,[u/2,q/2])-5,f=0;f<a.length;f++)e+=a[f].value;x(c,null,function(d){var g=-Math.PI/2,f=1,j=1;c.animation&&(c.animateScale&&(f=d),c.animateRotate&&(j=d));for(d=0;d<a.length;d++){var l=j*a[d].value/e*2*Math.PI;b.beginPath();b.arc(q/2,u/2,f*h,g,g+l);b.lineTo(q/2,u/2);b.closePath();b.fillStyle=a[d].color;b.fill();c.segmentShowStroke&&(b.lineWidth=
c.segmentStrokeWidth,b.strokeStyle=c.segmentStrokeColor,b.stroke());g+=l}},b)},J=function(a,c,b){for(var e=0,h=Math.min.apply(Math,[u/2,q/2])-5,f=h*(c.percentageInnerCutout/100),d=0;d<a.length;d++)e+=a[d].value;x(c,null,function(d){var k=-Math.PI/2,j=1,l=1;c.animation&&(c.animateScale&&(j=d),c.animateRotate&&(l=d));for(d=0;d<a.length;d++){var m=l*a[d].value/e*2*Math.PI;b.beginPath();b.arc(q/2,u/2,j*h,k,k+m,!1);b.arc(q/2,u/2,j*f,k+m,k,!0);b.closePath();b.fillStyle=a[d].color;b.fill();c.segmentShowStroke&&
(b.lineWidth=c.segmentStrokeWidth,b.strokeStyle=c.segmentStrokeColor,b.stroke());k+=m}},b)},K=function(a,c,b){var e,h,f,d,g,k,j,l,m,t,r,n,p,s=0;g=u;b.font=c.scaleFontStyle+" "+c.scaleFontSize+"px "+c.scaleFontFamily;t=1;for(d=0;d<a.labels.length;d++)e=b.measureText(a.labels[d]).width,t=e>t?e:t;q/a.labels.length<t?(s=45,q/a.labels.length<Math.cos(s)*t?(s=90,g-=t):g-=Math.sin(s)*t):g-=c.scaleFontSize;d=c.scaleFontSize;g=g-5-d;e=Number.MIN_VALUE;h=Number.MAX_VALUE;for(f=0;f<a.datasets.length;f++)for(l=
0;l<a.datasets[f].data.length;l++)a.datasets[f].data[l]>e&&(e=a.datasets[f].data[l]),a.datasets[f].data[l]<h&&(h=a.datasets[f].data[l]);f=Math.floor(g/(0.66*d));d=Math.floor(0.5*(g/d));l=c.scaleShowLabels?c.scaleLabel:"";c.scaleOverride?(j={steps:c.scaleSteps,stepValue:c.scaleStepWidth,graphMin:c.scaleStartValue,labels:[]},z(l,j.labels,j.steps,c.scaleStartValue,c.scaleStepWidth)):j=C(g,f,d,e,h,l);k=Math.floor(g/j.steps);d=1;if(c.scaleShowLabels){b.font=c.scaleFontStyle+" "+c.scaleFontSize+"px "+c.scaleFontFamily;
for(e=0;e<j.labels.length;e++)h=b.measureText(j.labels[e]).width,d=h>d?h:d;d+=10}r=q-d-t;m=Math.floor(r/(a.labels.length-1));n=q-t/2-r;p=g+c.scaleFontSize/2;x(c,function(){b.lineWidth=c.scaleLineWidth;b.strokeStyle=c.scaleLineColor;b.beginPath();b.moveTo(q-t/2+5,p);b.lineTo(q-t/2-r-5,p);b.stroke();0<s?(b.save(),b.textAlign="right"):b.textAlign="center";b.fillStyle=c.scaleFontColor;for(var d=0;d<a.labels.length;d++)b.save(),0<s?(b.translate(n+d*m,p+c.scaleFontSize),b.rotate(-(s*(Math.PI/180))),b.fillText(a.labels[d],
0,0),b.restore()):b.fillText(a.labels[d],n+d*m,p+c.scaleFontSize+3),b.beginPath(),b.moveTo(n+d*m,p+3),c.scaleShowGridLines&&0<d?(b.lineWidth=c.scaleGridLineWidth,b.strokeStyle=c.scaleGridLineColor,b.lineTo(n+d*m,5)):b.lineTo(n+d*m,p+3),b.stroke();b.lineWidth=c.scaleLineWidth;b.strokeStyle=c.scaleLineColor;b.beginPath();b.moveTo(n,p+5);b.lineTo(n,5);b.stroke();b.textAlign="right";b.textBaseline="middle";for(d=0;d<j.steps;d++)b.beginPath(),b.moveTo(n-3,p-(d+1)*k),c.scaleShowGridLines?(b.lineWidth=c.scaleGridLineWidth,
b.strokeStyle=c.scaleGridLineColor,b.lineTo(n+r+5,p-(d+1)*k)):b.lineTo(n-0.5,p-(d+1)*k),b.stroke(),c.scaleShowLabels&&b.fillText(j.labels[d],n-8,p-(d+1)*k)},function(d){function e(b,c){return p-d*v(a.datasets[b].data[c],j,k)}for(var f=0;f<a.datasets.length;f++){b.strokeStyle=a.datasets[f].strokeColor;b.lineWidth=c.datasetStrokeWidth;b.beginPath();b.moveTo(n,p-d*v(a.datasets[f].data[0],j,k));for(var g=1;g<a.datasets[f].data.length;g++)c.bezierCurve?b.bezierCurveTo(n+m*(g-0.5),e(f,g-1),n+m*(g-0.5),
e(f,g),n+m*g,e(f,g)):b.lineTo(n+m*g,e(f,g));b.stroke();c.datasetFill?(b.lineTo(n+m*(a.datasets[f].data.length-1),p),b.lineTo(n,p),b.closePath(),b.fillStyle=a.datasets[f].fillColor,b.fill()):b.closePath();if(c.pointDot){b.fillStyle=a.datasets[f].pointColor;b.strokeStyle=a.datasets[f].pointStrokeColor;b.lineWidth=c.pointDotStrokeWidth;for(g=0;g<a.datasets[f].data.length;g++)b.beginPath(),b.arc(n+m*g,p-d*v(a.datasets[f].data[g],j,k),c.pointDotRadius,0,2*Math.PI,!0),b.fill(),b.stroke()}}},b)},L=function(a,
c,b){var e,h,f,d,g,k,j,l,m,t,r,n,p,s,w=0;g=u;b.font=c.scaleFontStyle+" "+c.scaleFontSize+"px "+c.scaleFontFamily;t=1;for(d=0;d<a.labels.length;d++)e=b.measureText(a.labels[d]).width,t=e>t?e:t;q/a.labels.length<t?(w=45,q/a.labels.length<Math.cos(w)*t?(w=90,g-=t):g-=Math.sin(w)*t):g-=c.scaleFontSize;d=c.scaleFontSize;g=g-5-d;e=Number.MIN_VALUE;h=Number.MAX_VALUE;for(f=0;f<a.datasets.length;f++)for(l=0;l<a.datasets[f].data.length;l++)a.datasets[f].data[l]>e&&(e=a.datasets[f].data[l]),a.datasets[f].data[l]<
h&&(h=a.datasets[f].data[l]);f=Math.floor(g/(0.66*d));d=Math.floor(0.5*(g/d));l=c.scaleShowLabels?c.scaleLabel:"";c.scaleOverride?(j={steps:c.scaleSteps,stepValue:c.scaleStepWidth,graphMin:c.scaleStartValue,labels:[]},z(l,j.labels,j.steps,c.scaleStartValue,c.scaleStepWidth)):j=C(g,f,d,e,h,l);k=Math.floor(g/j.steps);d=1;if(c.scaleShowLabels){b.font=c.scaleFontStyle+" "+c.scaleFontSize+"px "+c.scaleFontFamily;for(e=0;e<j.labels.length;e++)h=b.measureText(j.labels[e]).width,d=h>d?h:d;d+=10}r=q-d-t;m=
Math.floor(r/a.labels.length);s=(m-2*c.scaleGridLineWidth-2*c.barValueSpacing-(c.barDatasetSpacing*a.datasets.length-1)-(c.barStrokeWidth/2*a.datasets.length-1))/a.datasets.length;n=q-t/2-r;p=g+c.scaleFontSize/2;x(c,function(){b.lineWidth=c.scaleLineWidth;b.strokeStyle=c.scaleLineColor;b.beginPath();b.moveTo(q-t/2+5,p);b.lineTo(q-t/2-r-5,p);b.stroke();0<w?(b.save(),b.textAlign="right"):b.textAlign="center";b.fillStyle=c.scaleFontColor;for(var d=0;d<a.labels.length;d++)b.save(),0<w?(b.translate(n+
d*m,p+c.scaleFontSize),b.rotate(-(w*(Math.PI/180))),b.fillText(a.labels[d],0,0),b.restore()):b.fillText(a.labels[d],n+d*m+m/2,p+c.scaleFontSize+3),b.beginPath(),b.moveTo(n+(d+1)*m,p+3),b.lineWidth=c.scaleGridLineWidth,b.strokeStyle=c.scaleGridLineColor,b.lineTo(n+(d+1)*m,5),b.stroke();b.lineWidth=c.scaleLineWidth;b.strokeStyle=c.scaleLineColor;b.beginPath();b.moveTo(n,p+5);b.lineTo(n,5);b.stroke();b.textAlign="right";b.textBaseline="middle";for(d=0;d<j.steps;d++)b.beginPath(),b.moveTo(n-3,p-(d+1)*
k),c.scaleShowGridLines?(b.lineWidth=c.scaleGridLineWidth,b.strokeStyle=c.scaleGridLineColor,b.lineTo(n+r+5,p-(d+1)*k)):b.lineTo(n-0.5,p-(d+1)*k),b.stroke(),c.scaleShowLabels&&b.fillText(j.labels[d],n-8,p-(d+1)*k)},function(d){b.lineWidth=c.barStrokeWidth;for(var e=0;e<a.datasets.length;e++){b.fillStyle=a.datasets[e].fillColor;b.strokeStyle=a.datasets[e].strokeColor;for(var f=0;f<a.datasets[e].data.length;f++){var g=n+c.barValueSpacing+m*f+s*e+c.barDatasetSpacing*e+c.barStrokeWidth*e;b.beginPath();
b.moveTo(g,p);b.lineTo(g,p-d*v(a.datasets[e].data[f],j,k)+c.barStrokeWidth/2);b.lineTo(g+s,p-d*v(a.datasets[e].data[f],j,k)+c.barStrokeWidth/2);b.lineTo(g+s,p);c.barShowStroke&&b.stroke();b.closePath();b.fill()}}},b)},D=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){window.setTimeout(a,1E3/60)},F={}};
\ No newline at end of file
# This is a manifest file that'll be compiled into application.js, which will include all the files
# listed below.
#
# Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
# or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
#
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
# WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
# GO AFTER THE REQUIRES BELOW.
#
#= require pager
#= require jquery_nested_form
#= require_tree .
#
$(document).on 'click', '.edit-runner-link', (event) ->
event.preventDefault()
descr = $(this).closest('.runner-description').first()
descr.addClass('hide')
form = descr.next('.runner-description-form')
descrInput = form.find('input.description')
originalValue = descrInput.val()
form.removeClass('hide')
form.find('.cancel').on 'click', (event) ->
event.preventDefault()
form.addClass('hide')
descrInput.val(originalValue)
descr.removeClass('hide')
$(document).on 'click', '.assign-all-runner', ->
$(this).replaceWith('<i class="fa fa-refresh fa-spin"></i> Assign in progress..')
window.unbindEvents = ->
$(document).unbind('scroll')
$(document).off('scroll')
document.addEventListener("page:fetch", unbindEvents)
class CiBuild
@interval: null
constructor: (build_url, build_status) ->
clearInterval(CiBuild.interval)
if build_status == "running" || build_status == "pending"
#
# Bind autoscroll button to follow build output
#
$("#autoscroll-button").bind "click", ->
state = $(this).data("state")
if "enabled" is state
$(this).data "state", "disabled"
$(this).text "enable autoscroll"
else
$(this).data "state", "enabled"
$(this).text "disable autoscroll"
#
# Check for new build output if user still watching build page
# Only valid for runnig build when output changes during time
#
CiBuild.interval = setInterval =>
if window.location.href is build_url
$.ajax
url: build_url
dataType: "json"
success: (build) =>
if build.status == "running"
$('#build-trace code').html build.trace_html
$('#build-trace code').append '<i class="fa fa-refresh fa-spin"/>'
@checkAutoscroll()
else
Turbolinks.visit build_url
, 4000
checkAutoscroll: ->
$("html,body").scrollTop $("#build-trace").height() if "enabled" is $("#autoscroll-button").data("state")
@CiBuild = CiBuild
@CiPager =
init: (@url, @limit = 0, preload, @disable = false) ->
if preload
@offset = 0
@getItems()
else
@offset = @limit
@initLoadMore()
getItems: ->
$(".loading").show()
$.ajax
type: "GET"
url: @url
data: "limit=" + @limit + "&offset=" + @offset
complete: =>
$(".loading").hide()
success: (data) =>
CiPager.append(data.count, data.html)
dataType: "json"
append: (count, html) ->
if count > 1
$(".content-list").append html
if count == @limit
@offset += count
else
@disable = true
initLoadMore: ->
$(document).unbind('scroll')
$(document).endlessScroll
bottomPixels: 400
fireDelay: 1000
fireOnce: true
ceaseFire: ->
CiPager.disable
callback: (i) =>
unless $(".loading").is(':visible')
$(".loading").show()
CiPager.getItems()
$(document).on 'click', '.badge-codes-toggle', ->
$('.badge-codes-block').toggleClass("hide")
return false
$(document).on 'click', '.sync-now', ->
$(this).find('i').addClass('fa-spin')
......@@ -61,3 +61,9 @@
* Styles for JS behaviors.
*/
@import "behaviors.scss";
/**
* CI specific styles:
*/
@import "ci/**/*";
.ci-body {
pre.trace {
background: #111111;
color: #fff;
font-family: $monospace_font;
white-space: pre;
white-space: pre-wrap; /* css-3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
overflow: auto;
overflow-y: hidden;
font-size: 12px;
.fa-refresh {
font-size: 24px;
margin-left: 20px;
}
}
.autoscroll-container {
position: fixed;
bottom: 10px;
right: 20px;
z-index: 100;
}
.scroll-controls {
position: fixed;
bottom: 10px;
left: 250px;
z-index: 100;
a {
display: block;
margin-bottom: 5px;
}
}
.page-sidebar-collapsed {
.scroll-controls {
left: 70px;
}
}
.build-widget {
padding: 10px;
background: $background-color;
margin-bottom: 20px;
border-radius: 4px;
.title {
margin-top: 0;
color: #666;
line-height: 1.5;
}
.attr-name {
color: #777;
}
}
.alert-disabled {
background: $background-color;
a {
color: #3084bb !important;
}
}
}
.ci-body {
.incorrect-syntax{
font-size: 19px;
color: red;
}
.correct-syntax{
font-size: 19px;
color: #47a447;
}
}
.ci-body {
.project-title {
margin: 0;
color: #444;
font-size: 20px;
line-height: 1.5;
}
.builds {
@extend .table;
.build {
&.alert{
margin-bottom: 6px;
}
}
}
.projects-table {
td {
vertical-align: middle !important;
}
}
.commit-info {
font-size: 14px;
.attr-name {
font-weight: 300;
color: #666;
margin-right: 5px;
}
pre.commit-message {
font-size: 14px;
background: none;
padding: 0;
margin: 0;
border: none;
margin: 20px 0;
border-bottom: 1px solid #EEE;
padding-bottom: 20px;
border-radius: 0;
}
}
.loading{
font-size: 20px;
}
.ci-charts {
fieldset {
margin-bottom: 16px;
}
}
}
.ci-body {
.runner-state {
padding: 6px 12px;
margin-right: 10px;
color: #FFF;
&.runner-state-shared {
background: #32b186;
}
&.runner-state-specific {
background: #3498db;
}
}
.runner-status-online {
color: green;
}
.runner-status-offline {
color: gray;
}
.runner-status-paused {
color: red;
}
.runner {
.btn {
padding: 1px 6px;
}
h4 {
font-weight: normal;
}
}
}
.ci-body {
// color codes are based on http://en.wikipedia.org/wiki/File:Xterm_256color_chart.svg
// see also: https://gist.github.com/jasonm23/2868981
$black: #000000;
$red: #cd0000;
$green: #00cd00;
$yellow: #cdcd00;
$blue: #0000ee; // according to wikipedia, this is the xterm standard
//$blue: #1e90ff; // this is used by all the terminals I tried (when configured with the xterm color profile)
$magenta: #cd00cd;
$cyan: #00cdcd;
$white: #e5e5e5;
$l-black: #7f7f7f;
$l-red: #ff0000;
$l-green: #00ff00;
$l-yellow: #ffff00;
$l-blue: #5c5cff;
$l-magenta: #ff00ff;
$l-cyan: #00ffff;
$l-white: #ffffff;
.term-bold {
font-weight: bold;
}
.term-italic {
font-style: italic;
}
.term-conceal {
visibility: hidden;
}
.term-underline {
text-decoration: underline;
}
.term-cross {
text-decoration: line-through;
}
.term-fg-black {
color: $black;
}
.term-fg-red {
color: $red;
}
.term-fg-green {
color: $green;
}
.term-fg-yellow {
color: $yellow;
}
.term-fg-blue {
color: $blue;
}
.term-fg-magenta {
color: $magenta;
}
.term-fg-cyan {
color: $cyan;
}
.term-fg-white {
color: $white;
}
.term-fg-l-black {
color: $l-black;
}
.term-fg-l-red {
color: $l-red;
}
.term-fg-l-green {
color: $l-green;
}
.term-fg-l-yellow {
color: $l-yellow;
}
.term-fg-l-blue {
color: $l-blue;
}
.term-fg-l-magenta {
color: $l-magenta;
}
.term-fg-l-cyan {
color: $l-cyan;
}
.term-fg-l-white {
color: $l-white;
}
.term-bg-black {
background-color: $black;
}
.term-bg-red {
background-color: $red;
}
.term-bg-green {
background-color: $green;
}
.term-bg-yellow {
background-color: $yellow;
}
.term-bg-blue {
background-color: $blue;
}
.term-bg-magenta {
background-color: $magenta;
}
.term-bg-cyan {
background-color: $cyan;
}
.term-bg-white {
background-color: $white;
}
.term-bg-l-black {
background-color: $l-black;
}
.term-bg-l-red {
background-color: $l-red;
}
.term-bg-l-green {
background-color: $l-green;
}
.term-bg-l-yellow {
background-color: $l-yellow;
}
.term-bg-l-blue {
background-color: $l-blue;
}
.term-bg-l-magenta {
background-color: $l-magenta;
}
.term-bg-l-cyan {
background-color: $l-cyan;
}
.term-bg-l-white {
background-color: $l-white;
}
.xterm-fg-0 {
color: #000000;
}
.xterm-fg-1 {
color: #800000;
}
.xterm-fg-2 {
color: #008000;
}
.xterm-fg-3 {
color: #808000;
}
.xterm-fg-4 {
color: #000080;
}
.xterm-fg-5 {
color: #800080;
}
.xterm-fg-6 {
color: #008080;
}
.xterm-fg-7 {
color: #c0c0c0;
}
.xterm-fg-8 {
color: #808080;
}
.xterm-fg-9 {
color: #ff0000;
}
.xterm-fg-10 {
color: #00ff00;
}
.xterm-fg-11 {
color: #ffff00;
}
.xterm-fg-12 {
color: #0000ff;
}
.xterm-fg-13 {
color: #ff00ff;
}
.xterm-fg-14 {
color: #00ffff;
}
.xterm-fg-15 {
color: #ffffff;
}
.xterm-fg-16 {
color: #000000;
}
.xterm-fg-17 {
color: #00005f;
}
.xterm-fg-18 {
color: #000087;
}
.xterm-fg-19 {
color: #0000af;
}
.xterm-fg-20 {
color: #0000d7;
}
.xterm-fg-21 {
color: #0000ff;
}
.xterm-fg-22 {
color: #005f00;
}
.xterm-fg-23 {
color: #005f5f;
}
.xterm-fg-24 {
color: #005f87;
}
.xterm-fg-25 {
color: #005faf;
}
.xterm-fg-26 {
color: #005fd7;
}
.xterm-fg-27 {
color: #005fff;
}
.xterm-fg-28 {
color: #008700;
}
.xterm-fg-29 {
color: #00875f;
}
.xterm-fg-30 {
color: #008787;
}
.xterm-fg-31 {
color: #0087af;
}
.xterm-fg-32 {
color: #0087d7;
}
.xterm-fg-33 {
color: #0087ff;
}
.xterm-fg-34 {
color: #00af00;
}
.xterm-fg-35 {
color: #00af5f;
}
.xterm-fg-36 {
color: #00af87;
}
.xterm-fg-37 {
color: #00afaf;
}
.xterm-fg-38 {
color: #00afd7;
}
.xterm-fg-39 {
color: #00afff;
}
.xterm-fg-40 {
color: #00d700;
}
.xterm-fg-41 {
color: #00d75f;
}
.xterm-fg-42 {
color: #00d787;
}
.xterm-fg-43 {
color: #00d7af;
}
.xterm-fg-44 {
color: #00d7d7;
}
.xterm-fg-45 {
color: #00d7ff;
}
.xterm-fg-46 {
color: #00ff00;
}
.xterm-fg-47 {
color: #00ff5f;
}
.xterm-fg-48 {
color: #00ff87;
}
.xterm-fg-49 {
color: #00ffaf;
}
.xterm-fg-50 {
color: #00ffd7;
}
.xterm-fg-51 {
color: #00ffff;
}
.xterm-fg-52 {
color: #5f0000;
}
.xterm-fg-53 {
color: #5f005f;
}
.xterm-fg-54 {
color: #5f0087;
}
.xterm-fg-55 {
color: #5f00af;
}
.xterm-fg-56 {
color: #5f00d7;
}
.xterm-fg-57 {
color: #5f00ff;
}
.xterm-fg-58 {
color: #5f5f00;
}
.xterm-fg-59 {
color: #5f5f5f;
}
.xterm-fg-60 {
color: #5f5f87;
}
.xterm-fg-61 {
color: #5f5faf;
}
.xterm-fg-62 {
color: #5f5fd7;
}
.xterm-fg-63 {
color: #5f5fff;
}
.xterm-fg-64 {
color: #5f8700;
}
.xterm-fg-65 {
color: #5f875f;
}
.xterm-fg-66 {
color: #5f8787;
}
.xterm-fg-67 {
color: #5f87af;
}
.xterm-fg-68 {
color: #5f87d7;
}
.xterm-fg-69 {
color: #5f87ff;
}
.xterm-fg-70 {
color: #5faf00;
}
.xterm-fg-71 {
color: #5faf5f;
}
.xterm-fg-72 {
color: #5faf87;
}
.xterm-fg-73 {
color: #5fafaf;
}
.xterm-fg-74 {
color: #5fafd7;
}
.xterm-fg-75 {
color: #5fafff;
}
.xterm-fg-76 {
color: #5fd700;
}
.xterm-fg-77 {
color: #5fd75f;
}
.xterm-fg-78 {
color: #5fd787;
}
.xterm-fg-79 {
color: #5fd7af;
}
.xterm-fg-80 {
color: #5fd7d7;
}
.xterm-fg-81 {
color: #5fd7ff;
}
.xterm-fg-82 {
color: #5fff00;
}
.xterm-fg-83 {
color: #5fff5f;
}
.xterm-fg-84 {
color: #5fff87;
}
.xterm-fg-85 {
color: #5fffaf;
}
.xterm-fg-86 {
color: #5fffd7;
}
.xterm-fg-87 {
color: #5fffff;
}
.xterm-fg-88 {
color: #870000;
}
.xterm-fg-89 {
color: #87005f;
}
.xterm-fg-90 {
color: #870087;
}
.xterm-fg-91 {
color: #8700af;
}
.xterm-fg-92 {
color: #8700d7;
}
.xterm-fg-93 {
color: #8700ff;
}
.xterm-fg-94 {
color: #875f00;
}
.xterm-fg-95 {
color: #875f5f;
}
.xterm-fg-96 {
color: #875f87;
}
.xterm-fg-97 {
color: #875faf;
}
.xterm-fg-98 {
color: #875fd7;
}
.xterm-fg-99 {
color: #875fff;
}
.xterm-fg-100 {
color: #878700;
}
.xterm-fg-101 {
color: #87875f;
}
.xterm-fg-102 {
color: #878787;
}
.xterm-fg-103 {
color: #8787af;
}
.xterm-fg-104 {
color: #8787d7;
}
.xterm-fg-105 {
color: #8787ff;
}
.xterm-fg-106 {
color: #87af00;
}
.xterm-fg-107 {
color: #87af5f;
}
.xterm-fg-108 {
color: #87af87;
}
.xterm-fg-109 {
color: #87afaf;
}
.xterm-fg-110 {
color: #87afd7;
}
.xterm-fg-111 {
color: #87afff;
}
.xterm-fg-112 {
color: #87d700;
}
.xterm-fg-113 {
color: #87d75f;
}
.xterm-fg-114 {
color: #87d787;
}
.xterm-fg-115 {
color: #87d7af;
}
.xterm-fg-116 {
color: #87d7d7;
}
.xterm-fg-117 {
color: #87d7ff;
}
.xterm-fg-118 {
color: #87ff00;
}
.xterm-fg-119 {
color: #87ff5f;
}
.xterm-fg-120 {
color: #87ff87;
}
.xterm-fg-121 {
color: #87ffaf;
}
.xterm-fg-122 {
color: #87ffd7;
}
.xterm-fg-123 {
color: #87ffff;
}
.xterm-fg-124 {
color: #af0000;
}
.xterm-fg-125 {
color: #af005f;
}
.xterm-fg-126 {
color: #af0087;
}
.xterm-fg-127 {
color: #af00af;
}
.xterm-fg-128 {
color: #af00d7;
}
.xterm-fg-129 {
color: #af00ff;
}
.xterm-fg-130 {
color: #af5f00;
}
.xterm-fg-131 {
color: #af5f5f;
}
.xterm-fg-132 {
color: #af5f87;
}
.xterm-fg-133 {
color: #af5faf;
}
.xterm-fg-134 {
color: #af5fd7;
}
.xterm-fg-135 {
color: #af5fff;
}
.xterm-fg-136 {
color: #af8700;
}
.xterm-fg-137 {
color: #af875f;
}
.xterm-fg-138 {
color: #af8787;
}
.xterm-fg-139 {
color: #af87af;
}
.xterm-fg-140 {
color: #af87d7;
}
.xterm-fg-141 {
color: #af87ff;
}
.xterm-fg-142 {
color: #afaf00;
}
.xterm-fg-143 {
color: #afaf5f;
}
.xterm-fg-144 {
color: #afaf87;
}
.xterm-fg-145 {
color: #afafaf;
}
.xterm-fg-146 {
color: #afafd7;
}
.xterm-fg-147 {
color: #afafff;
}
.xterm-fg-148 {
color: #afd700;
}
.xterm-fg-149 {
color: #afd75f;
}
.xterm-fg-150 {
color: #afd787;
}
.xterm-fg-151 {
color: #afd7af;
}
.xterm-fg-152 {
color: #afd7d7;
}
.xterm-fg-153 {
color: #afd7ff;
}
.xterm-fg-154 {
color: #afff00;
}
.xterm-fg-155 {
color: #afff5f;
}
.xterm-fg-156 {
color: #afff87;
}
.xterm-fg-157 {
color: #afffaf;
}
.xterm-fg-158 {
color: #afffd7;
}
.xterm-fg-159 {
color: #afffff;
}
.xterm-fg-160 {
color: #d70000;
}
.xterm-fg-161 {
color: #d7005f;
}
.xterm-fg-162 {
color: #d70087;
}
.xterm-fg-163 {
color: #d700af;
}
.xterm-fg-164 {
color: #d700d7;
}
.xterm-fg-165 {
color: #d700ff;
}
.xterm-fg-166 {
color: #d75f00;
}
.xterm-fg-167 {
color: #d75f5f;
}
.xterm-fg-168 {
color: #d75f87;
}
.xterm-fg-169 {
color: #d75faf;
}
.xterm-fg-170 {
color: #d75fd7;
}
.xterm-fg-171 {
color: #d75fff;
}
.xterm-fg-172 {
color: #d78700;
}
.xterm-fg-173 {
color: #d7875f;
}
.xterm-fg-174 {
color: #d78787;
}
.xterm-fg-175 {
color: #d787af;
}
.xterm-fg-176 {
color: #d787d7;
}
.xterm-fg-177 {
color: #d787ff;
}
.xterm-fg-178 {
color: #d7af00;
}
.xterm-fg-179 {
color: #d7af5f;
}
.xterm-fg-180 {
color: #d7af87;
}
.xterm-fg-181 {
color: #d7afaf;
}
.xterm-fg-182 {
color: #d7afd7;
}
.xterm-fg-183 {
color: #d7afff;
}
.xterm-fg-184 {
color: #d7d700;
}
.xterm-fg-185 {
color: #d7d75f;
}
.xterm-fg-186 {
color: #d7d787;
}
.xterm-fg-187 {
color: #d7d7af;
}
.xterm-fg-188 {
color: #d7d7d7;
}
.xterm-fg-189 {
color: #d7d7ff;
}
.xterm-fg-190 {
color: #d7ff00;
}
.xterm-fg-191 {
color: #d7ff5f;
}
.xterm-fg-192 {
color: #d7ff87;
}
.xterm-fg-193 {
color: #d7ffaf;
}
.xterm-fg-194 {
color: #d7ffd7;
}
.xterm-fg-195 {
color: #d7ffff;
}
.xterm-fg-196 {
color: #ff0000;
}
.xterm-fg-197 {
color: #ff005f;
}
.xterm-fg-198 {
color: #ff0087;
}
.xterm-fg-199 {
color: #ff00af;
}
.xterm-fg-200 {
color: #ff00d7;
}
.xterm-fg-201 {
color: #ff00ff;
}
.xterm-fg-202 {
color: #ff5f00;
}
.xterm-fg-203 {
color: #ff5f5f;
}
.xterm-fg-204 {
color: #ff5f87;
}
.xterm-fg-205 {
color: #ff5faf;
}
.xterm-fg-206 {
color: #ff5fd7;
}
.xterm-fg-207 {
color: #ff5fff;
}
.xterm-fg-208 {
color: #ff8700;
}
.xterm-fg-209 {
color: #ff875f;
}
.xterm-fg-210 {
color: #ff8787;
}
.xterm-fg-211 {
color: #ff87af;
}
.xterm-fg-212 {
color: #ff87d7;
}
.xterm-fg-213 {
color: #ff87ff;
}
.xterm-fg-214 {
color: #ffaf00;
}
.xterm-fg-215 {
color: #ffaf5f;
}
.xterm-fg-216 {
color: #ffaf87;
}
.xterm-fg-217 {
color: #ffafaf;
}
.xterm-fg-218 {
color: #ffafd7;
}
.xterm-fg-219 {
color: #ffafff;
}
.xterm-fg-220 {
color: #ffd700;
}
.xterm-fg-221 {
color: #ffd75f;
}
.xterm-fg-222 {
color: #ffd787;
}
.xterm-fg-223 {
color: #ffd7af;
}
.xterm-fg-224 {
color: #ffd7d7;
}
.xterm-fg-225 {
color: #ffd7ff;
}
.xterm-fg-226 {
color: #ffff00;
}
.xterm-fg-227 {
color: #ffff5f;
}
.xterm-fg-228 {
color: #ffff87;
}
.xterm-fg-229 {
color: #ffffaf;
}
.xterm-fg-230 {
color: #ffffd7;
}
.xterm-fg-231 {
color: #ffffff;
}
.xterm-fg-232 {
color: #080808;
}
.xterm-fg-233 {
color: #121212;
}
.xterm-fg-234 {
color: #1c1c1c;
}
.xterm-fg-235 {
color: #262626;
}
.xterm-fg-236 {
color: #303030;
}
.xterm-fg-237 {
color: #3a3a3a;
}
.xterm-fg-238 {
color: #444444;
}
.xterm-fg-239 {
color: #4e4e4e;
}
.xterm-fg-240 {
color: #585858;
}
.xterm-fg-241 {
color: #626262;
}
.xterm-fg-242 {
color: #6c6c6c;
}
.xterm-fg-243 {
color: #767676;
}
.xterm-fg-244 {
color: #808080;
}
.xterm-fg-245 {
color: #8a8a8a;
}
.xterm-fg-246 {
color: #949494;
}
.xterm-fg-247 {
color: #9e9e9e;
}
.xterm-fg-248 {
color: #a8a8a8;
}
.xterm-fg-249 {
color: #b2b2b2;
}
.xterm-fg-250 {
color: #bcbcbc;
}
.xterm-fg-251 {
color: #c6c6c6;
}
.xterm-fg-252 {
color: #d0d0d0;
}
.xterm-fg-253 {
color: #dadada;
}
.xterm-fg-254 {
color: #e4e4e4;
}
.xterm-fg-255 {
color: #eeeeee;
}
}
......@@ -134,9 +134,6 @@ class ApplicationController < ActionController::Base
def repository
@repository ||= project.repository
rescue Grit::NoSuchPathError => e
log_exception(e)
nil
end
def authorize_project!(action)
......
module Ci
module Admin
class ApplicationController < Ci::ApplicationController
before_action :authenticate_user!
before_action :authenticate_admin!
layout "ci/admin"
end
end
end
module Ci
class Admin::ApplicationSettingsController < Ci::Admin::ApplicationController
before_action :set_application_setting
def show
end
def update
if @application_setting.update_attributes(application_setting_params)
redirect_to ci_admin_application_settings_path,
notice: 'Application settings saved successfully'
else
render :show
end
end
private
def set_application_setting
@application_setting = Ci::ApplicationSetting.current
@application_setting ||= Ci::ApplicationSetting.create_from_defaults
end
def application_setting_params
params.require(:application_setting).permit(
:all_broken_builds,
:add_pusher,
)
end
end
end
module Ci
class Admin::BuildsController < Ci::Admin::ApplicationController
def index
@scope = params[:scope]
@builds = Ci::Build.order('created_at DESC').page(params[:page]).per(30)
@builds =
case @scope
when "pending"
@builds.pending
when "running"
@builds.running
else
@builds
end
end
end
end
module Ci
class Admin::EventsController < Ci::Admin::ApplicationController
EVENTS_PER_PAGE = 50
def index
@events = Ci::Event.admin.order('created_at DESC').page(params[:page]).per(EVENTS_PER_PAGE)
end
end
end
module Ci
class Admin::ProjectsController < Ci::Admin::ApplicationController
def index
@projects = Ci::Project.ordered_by_last_commit_date.page(params[:page]).per(30)
end
def destroy
project.destroy
redirect_to ci_projects_url
end
protected
def project
@project ||= Ci::Project.find(params[:id])
end
end
end
module Ci
class Admin::RunnerProjectsController < Ci::Admin::ApplicationController
layout 'ci/project'
def index
@runner_projects = project.runner_projects.all
@runner_project = project.runner_projects.new
end
def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id])
if @runner.assign_to(project, current_user)
redirect_to ci_admin_runner_path(@runner)
else
redirect_to ci_admin_runner_path(@runner), alert: 'Failed adding runner to project'
end
end
def destroy
rp = Ci::RunnerProject.find(params[:id])
runner = rp.runner
rp.destroy
redirect_to ci_admin_runner_path(runner)
end
private
def project
@project ||= Ci::Project.find(params[:project_id])
end
end
end
module Ci
class Admin::RunnersController < Ci::Admin::ApplicationController
before_action :runner, except: :index
def index
@runners = Ci::Runner.order('id DESC')
@runners = @runners.search(params[:search]) if params[:search].present?
@runners = @runners.page(params[:page]).per(30)
@active_runners_cnt = Ci::Runner.where("contacted_at > ?", 1.minutes.ago).count
end
def show
@builds = @runner.builds.order('id DESC').first(30)
@projects = Ci::Project.all
@projects = @projects.search(params[:search]) if params[:search].present?
@projects = @projects.where("ci_projects.id NOT IN (?)", @runner.projects.pluck(:id)) if @runner.projects.any?
@projects = @projects.page(params[:page]).per(30)
end
def update
@runner.update_attributes(runner_params)
respond_to do |format|
format.js
format.html { redirect_to ci_admin_runner_path(@runner) }
end
end
def destroy
@runner.destroy
redirect_to ci_admin_runners_path
end
def resume
if @runner.update_attributes(active: true)
redirect_to ci_admin_runners_path, notice: 'Runner was successfully updated.'
else
redirect_to ci_admin_runners_path, alert: 'Runner was not updated.'
end
end
def pause
if @runner.update_attributes(active: false)
redirect_to ci_admin_runners_path, notice: 'Runner was successfully updated.'
else
redirect_to ci_admin_runners_path, alert: 'Runner was not updated.'
end
end
def assign_all
Ci::Project.unassigned(@runner).all.each do |project|
@runner.assign_to(project, current_user)
end
redirect_to ci_admin_runner_path(@runner), notice: "Runner was assigned to all projects"
end
private
def runner
@runner ||= Ci::Runner.find(params[:id])
end
def runner_params
params.require(:runner).permit(:token, :description, :tag_list, :contacted_at, :active)
end
end
end
module Ci
class ApplicationController < ::ApplicationController
def self.railtie_helpers_paths
"app/helpers/ci"
end
helper_method :gl_project
private
def authenticate_public_page!
unless project.public
unless current_user
redirect_to(new_user_sessions_path) and return
end
return access_denied! unless can?(current_user, :read_project, gl_project)
end
end
def authenticate_token!
unless project.valid_token?(params[:token])
return head(403)
end
end
def authorize_access_project!
unless can?(current_user, :read_project, gl_project)
return page_404
end
end
def authorize_manage_builds!
unless can?(current_user, :admin_project, gl_project)
return page_404
end
end
def authenticate_admin!
return render_404 unless current_user.is_admin?
end
def authorize_manage_project!
unless can?(current_user, :admin_project, gl_project)
return page_404
end
end
def page_404
render file: "#{Rails.root}/public/404.html", status: 404, layout: false
end
def default_headers
headers['X-Frame-Options'] = 'DENY'
headers['X-XSS-Protection'] = '1; mode=block'
end
# JSON for infinite scroll via Pager object
def pager_json(partial, count)
html = render_to_string(
partial,
layout: false,
formats: [:html]
)
render json: {
html: html,
count: count
}
end
def gl_project
::Project.find(@project.gitlab_id)
end
end
end
module Ci
class BuildsController < Ci::ApplicationController
before_action :authenticate_user!, except: [:status, :show]
before_action :authenticate_public_page!, only: :show
before_action :project
before_action :authorize_access_project!, except: [:status, :show]
before_action :authorize_manage_project!, except: [:status, :show, :retry, :cancel]
before_action :authorize_manage_builds!, only: [:retry, :cancel]
before_action :build, except: [:show]
layout 'ci/build'
def show
if params[:id] =~ /\A\d+\Z/
@build = build
else
# try to find commit by sha
commit = commit_by_sha
if commit
# Redirect to commit page
redirect_to ci_project_ref_commit_path(@project, @build.commit.ref, @build.commit.sha)
return
end
end
raise ActiveRecord::RecordNotFound unless @build
@builds = @project.commits.find_by_sha(@build.sha).builds.order('id DESC')
@builds = @builds.where("id not in (?)", @build.id).page(params[:page]).per(20)
@commit = @build.commit
respond_to do |format|
format.html
format.json do
render json: @build.to_json(methods: :trace_html)
end
end
end
def retry
if @build.commands.blank?
return page_404
end
build = Ci::Build.retry(@build)
if params[:return_to]
redirect_to URI.parse(params[:return_to]).path
else
redirect_to ci_project_build_path(project, build)
end
end
def status
render json: @build.to_json(only: [:status, :id, :sha, :coverage], methods: :sha)
end
def cancel
@build.cancel
redirect_to ci_project_build_path(@project, @build)
end
protected
def project
@project = Ci::Project.find(params[:project_id])
end
def build
@build ||= project.builds.unscoped.find_by(id: params[:id])
end
def commit_by_sha
@project.commits.find_by(sha: params[:id])
end
end
end
module Ci
class ChartsController < Ci::ApplicationController
before_action :authenticate_user!
before_action :project
before_action :authorize_access_project!
before_action :authorize_manage_project!
layout 'ci/project'
def show
@charts = {}
@charts[:week] = Ci::Charts::WeekChart.new(@project)
@charts[:month] = Ci::Charts::MonthChart.new(@project)
@charts[:year] = Ci::Charts::YearChart.new(@project)
@charts[:build_times] = Ci::Charts::BuildTime.new(@project)
end
protected
def project
@project = Ci::Project.find(params[:project_id])
end
end
end
module Ci
class CommitsController < Ci::ApplicationController
before_action :authenticate_user!, except: [:status, :show]
before_action :authenticate_public_page!, only: :show
before_action :project
before_action :authorize_access_project!, except: [:status, :show, :cancel]
before_action :authorize_manage_builds!, only: [:cancel]
before_action :commit, only: :show
layout 'ci/commit'
def show
@builds = @commit.builds
end
def status
commit = Ci::Project.find(params[:project_id]).commits.find_by_sha_and_ref!(params[:id], params[:ref_id])
render json: commit.to_json(only: [:id, :sha], methods: [:status, :coverage])
rescue ActiveRecord::RecordNotFound
render json: { status: "not_found" }
end
def cancel
commit.builds.running_or_pending.each(&:cancel)
redirect_to ci_project_ref_commits_path(project, commit.ref, commit.sha)
end
private
def project
@project ||= Ci::Project.find(params[:project_id])
end
def commit
@commit ||= Ci::Project.find(params[:project_id]).commits.find_by_sha_and_ref!(params[:id], params[:ref_id])
end
end
end
module Ci
class EventsController < Ci::ApplicationController
EVENTS_PER_PAGE = 50
before_action :authenticate_user!
before_action :project
before_action :authorize_manage_project!
layout 'ci/project'
def index
@events = project.events.order("created_at DESC").page(params[:page]).per(EVENTS_PER_PAGE)
end
private
def project
@project ||= Ci::Project.find(params[:project_id])
end
end
end
module Ci
class HelpsController < Ci::ApplicationController
skip_filter :check_config
def show
end
def oauth2
if valid_config?
redirect_to ci_root_path
else
render layout: 'ci/empty'
end
end
end
end
module Ci
class LintsController < Ci::ApplicationController
before_action :authenticate_user!
def show
end
def create
if params[:content].blank?
@status = false
@error = "Please provide content of .gitlab-ci.yml"
else
@config_processor = Ci::GitlabCiYamlProcessor.new params[:content]
@stages = @config_processor.stages
@builds = @config_processor.builds
@status = true
end
rescue Ci::GitlabCiYamlProcessor::ValidationError => e
@error = e.message
@status = false
rescue Exception => e
@error = "Undefined error"
@status = false
end
end
end
module Ci
class ProjectsController < Ci::ApplicationController
PROJECTS_BATCH = 100
before_action :authenticate_user!, except: [:build, :badge, :index, :show]
before_action :authenticate_public_page!, only: :show
before_action :project, only: [:build, :integration, :show, :badge, :edit, :update, :destroy, :toggle_shared_runners, :dumped_yaml]
before_action :authorize_access_project!, except: [:build, :gitlab, :badge, :index, :show, :new, :create]
before_action :authorize_manage_project!, only: [:edit, :integration, :update, :destroy, :toggle_shared_runners, :dumped_yaml]
before_action :authenticate_token!, only: [:build]
before_action :no_cache, only: [:badge]
protect_from_forgery except: :build
layout 'ci/project', except: [:index, :gitlab]
def index
@projects = Ci::Project.ordered_by_last_commit_date.public_only.page(params[:page]) unless current_user
end
def gitlab
@limit, @offset = (params[:limit] || PROJECTS_BATCH).to_i, (params[:offset] || 0).to_i
@page = @offset == 0 ? 1 : (@offset / @limit + 1)
@gl_projects = current_user.authorized_projects
@gl_projects = @gl_projects.where("name LIKE ?", "%#{params[:search]}%") if params[:search]
@gl_projects = @gl_projects.page(@page).per(@limit)
@projects = Ci::Project.where(gitlab_id: @gl_projects.map(&:id)).ordered_by_last_commit_date
@total_count = @gl_projects.size
@gl_projects = @gl_projects.where.not(id: @projects.map(&:gitlab_id))
respond_to do |format|
format.json do
pager_json("ci/projects/gitlab", @total_count)
end
end
rescue
@error = 'Failed to fetch GitLab projects'
end
def show
@ref = params[:ref]
@commits = @project.commits.reverse_order
@commits = @commits.where(ref: @ref) if @ref
@commits = @commits.page(params[:page]).per(20)
end
def integration
end
def create
project_data = OpenStruct.new(JSON.parse(params["project"]))
unless can?(current_user, :admin_project, ::Project.find(project_data.id))
return redirect_to ci_root_path, alert: 'You have to have at least master role to enable CI for this project'
end
@project = Ci::CreateProjectService.new.execute(current_user, project_data, ci_project_url(":project_id"))
if @project.persisted?
redirect_to ci_project_path(@project, show_guide: true), notice: 'Project was successfully created.'
else
redirect_to :back, alert: 'Cannot save project'
end
end
def edit
end
def update
if project.update_attributes(project_params)
Ci::EventService.new.change_project_settings(current_user, project)
redirect_to :back, notice: 'Project was successfully updated.'
else
render action: "edit"
end
end
def destroy
project.gl_project.gitlab_ci_service.update_attributes(active: false)
project.destroy
Ci::EventService.new.remove_project(current_user, project)
redirect_to ci_projects_url
end
def build
@commit = Ci::CreateCommitService.new.execute(@project, params.dup)
if @commit && @commit.valid?
head 201
else
head 400
end
end
# Project status badge
# Image with build status for sha or ref
def badge
image = Ci::ImageForBuildService.new.execute(@project, params)
send_file image.path, filename: image.name, disposition: 'inline', type:"image/svg+xml"
end
def toggle_shared_runners
project.toggle!(:shared_runners_enabled)
redirect_to :back
end
def dumped_yaml
send_data @project.generated_yaml_config, filename: '.gitlab-ci.yml'
end
protected
def project
@project ||= Ci::Project.find(params[:id])
end
def no_cache
response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
end
def project_params
params.require(:project).permit(:path, :timeout, :timeout_in_minutes, :default_ref, :always_build,
:polling_interval, :public, :ssh_url_to_repo, :allow_git_fetch, :email_recipients,
:email_add_pusher, :email_only_broken_builds, :coverage_regex, :shared_runners_enabled, :token,
{ variables_attributes: [:id, :key, :value, :_destroy] })
end
end
end
module Ci
class RunnerProjectsController < Ci::ApplicationController
before_action :authenticate_user!
before_action :project
before_action :authorize_manage_project!
layout 'ci/project'
def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id])
return head(403) unless current_user.ci_authorized_runners.include?(@runner)
if @runner.assign_to(project, current_user)
redirect_to ci_project_runners_path(project)
else
redirect_to ci_project_runners_path(project), alert: 'Failed adding runner to project'
end
end
def destroy
runner_project = project.runner_projects.find(params[:id])
runner_project.destroy
redirect_to ci_project_runners_path(project)
end
private
def project
@project ||= Ci::Project.find(params[:project_id])
end
end
end
module Ci
class RunnersController < Ci::ApplicationController
before_action :authenticate_user!
before_action :project
before_action :set_runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
before_action :authorize_access_project!
before_action :authorize_manage_project!
layout 'ci/project'
def index
@runners = @project.runners.order('id DESC')
@specific_runners =
Ci::Runner.specific.includes(:runner_projects).
where(Ci::RunnerProject.table_name => { project_id: current_user.authorized_projects } ).
where.not(id: @runners).order("#{Ci::Runner.table_name}.id DESC").page(params[:page]).per(20)
@shared_runners = Ci::Runner.shared.active
@shared_runners_count = @shared_runners.count(:all)
end
def edit
end
def update
if @runner.update_attributes(runner_params)
redirect_to edit_ci_project_runner_path(@project, @runner), notice: 'Runner was successfully updated.'
else
redirect_to edit_ci_project_runner_path(@project, @runner), alert: 'Runner was not updated.'
end
end
def destroy
if @runner.only_for?(@project)
@runner.destroy
end
redirect_to ci_project_runners_path(@project)
end
def resume
if @runner.update_attributes(active: true)
redirect_to ci_project_runners_path(@project, @runner), notice: 'Runner was successfully updated.'
else
redirect_to ci_project_runners_path(@project, @runner), alert: 'Runner was not updated.'
end
end
def pause
if @runner.update_attributes(active: false)
redirect_to ci_project_runners_path(@project, @runner), notice: 'Runner was successfully updated.'
else
redirect_to ci_project_runners_path(@project, @runner), alert: 'Runner was not updated.'
end
end
def show
end
protected
def project
@project = Ci::Project.find(params[:project_id])
end
def set_runner
@runner ||= @project.runners.find(params[:id])
end
def runner_params
params.require(:runner).permit(:description, :tag_list, :contacted_at, :active)
end
end
end
module Ci
class ServicesController < Ci::ApplicationController
before_action :authenticate_user!
before_action :project
before_action :authorize_access_project!
before_action :authorize_manage_project!
before_action :service, only: [:edit, :update, :test]
respond_to :html
layout 'ci/project'
def index
@project.build_missing_services
@services = @project.services.reload
end
def edit
end
def update
if @service.update_attributes(service_params)
redirect_to edit_ci_project_service_path(@project, @service.to_param)
else
render 'edit'
end
end
def test
last_build = @project.builds.last
if @service.execute(last_build)
message = { notice: 'We successfully tested the service' }
else
message = { alert: 'We tried to test the service but error occurred' }
end
redirect_to :back, message
end
private
def project
@project = Ci::Project.find(params[:project_id])
end
def service
@service ||= @project.services.find { |service| service.to_param == params[:id] }
end
def service_params
params.require(:service).permit(
:type, :active, :webhook, :notify_only_broken_builds,
:email_recipients, :email_only_broken_builds, :email_add_pusher,
:hipchat_token, :hipchat_room, :hipchat_server
)
end
end
end
module Ci
class TriggersController < Ci::ApplicationController
before_action :authenticate_user!
before_action :project
before_action :authorize_access_project!
before_action :authorize_manage_project!
layout 'ci/project'
def index
@triggers = @project.triggers
@trigger = Ci::Trigger.new
end
def create
@trigger = @project.triggers.new
@trigger.save
if @trigger.valid?
redirect_to ci_project_triggers_path(@project)
else
@triggers = @project.triggers.select(&:persisted?)
render :index
end
end
def destroy
trigger.destroy
redirect_to ci_project_triggers_path(@project)
end
private
def trigger
@trigger ||= @project.triggers.find(params[:id])
end
def project
@project = Ci::Project.find(params[:project_id])
end
end
end
module Ci
class VariablesController < Ci::ApplicationController
before_action :authenticate_user!
before_action :project
before_action :authorize_access_project!
before_action :authorize_manage_project!
layout 'ci/project'
def show
end
def update
if project.update_attributes(project_params)
Ci::EventService.new.change_project_settings(current_user, project)
redirect_to ci_project_variables_path(project), notice: 'Variables were successfully updated.'
else
render action: 'show'
end
end
private
def project
@project ||= Ci::Project.find(params[:project_id])
end
def project_params
params.require(:project).permit({ variables_attributes: [:id, :key, :value, :_destroy] })
end
end
end
module Ci
class WebHooksController < Ci::ApplicationController
before_action :authenticate_user!
before_action :project
before_action :authorize_access_project!
before_action :authorize_manage_project!
layout 'ci/project'
def index
@web_hooks = @project.web_hooks
@web_hook = Ci::WebHook.new
end
def create
@web_hook = @project.web_hooks.new(web_hook_params)
@web_hook.save
if @web_hook.valid?
redirect_to ci_project_web_hooks_path(@project)
else
@web_hooks = @project.web_hooks.select(&:persisted?)
render :index
end
end
def test
Ci::TestHookService.new.execute(hook, current_user)
redirect_to :back
end
def destroy
hook.destroy
redirect_to ci_project_web_hooks_path(@project)
end
private
def hook
@web_hook ||= @project.web_hooks.find(params[:id])
end
def project
@project = Ci::Project.find(params[:project_id])
end
def web_hook_params
params.require(:web_hook).permit(:url)
end
end
end
class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
include Gitlab::CurrentSettings
include PageLayoutHelper
before_action :verify_user_oauth_applications_enabled
before_action :authenticate_user!
......
module Ci
module ApplicationHelper
def loader_html
image_tag 'ci/loader.gif', alt: 'Loading'
end
# Navigation link helper
#
# Returns an `li` element with an 'active' class if the supplied
# controller(s) and/or action(s) are currently active. The content of the
# element is the value passed to the block.
#
# options - The options hash used to determine if the element is "active" (default: {})
# :controller - One or more controller names to check (optional).
# :action - One or more action names to check (optional).
# :path - A shorthand path, such as 'dashboard#index', to check (optional).
# :html_options - Extra options to be passed to the list element (optional).
# block - An optional block that will become the contents of the returned
# `li` element.
#
# When both :controller and :action are specified, BOTH must match in order
# to be marked as active. When only one is given, either can match.
#
# Examples
#
# # Assuming we're on TreeController#show
#
# # Controller matches, but action doesn't
# nav_link(controller: [:tree, :refs], action: :edit) { "Hello" }
# # => '<li>Hello</li>'
#
# # Controller matches
# nav_link(controller: [:tree, :refs]) { "Hello" }
# # => '<li class="active">Hello</li>'
#
# # Shorthand path
# nav_link(path: 'tree#show') { "Hello" }
# # => '<li class="active">Hello</li>'
#
# # Supplying custom options for the list element
# nav_link(controller: :tree, html_options: {class: 'home'}) { "Hello" }
# # => '<li class="home active">Hello</li>'
#
# Returns a list item element String
def nav_link(options = {}, &block)
if path = options.delete(:path)
if path.respond_to?(:each)
c = path.map { |p| p.split('#').first }
a = path.map { |p| p.split('#').last }
else
c, a, _ = path.split('#')
end
else
c = options.delete(:controller)
a = options.delete(:action)
end
if c && a
# When given both options, make sure BOTH are active
klass = current_controller?(*c) && current_action?(*a) ? 'active' : ''
else
# Otherwise check EITHER option
klass = current_controller?(*c) || current_action?(*a) ? 'active' : ''
end
# Add our custom class into the html_options, which may or may not exist
# and which may or may not already have a :class key
o = options.delete(:html_options) || {}
o[:class] ||= ''
o[:class] += ' ' + klass
o[:class].strip!
if block_given?
content_tag(:li, capture(&block), o)
else
content_tag(:li, nil, o)
end
end
# Check if a particular controller is the current one
#
# args - One or more controller names to check
#
# Examples
#
# # On TreeController
# current_controller?(:tree) # => true
# current_controller?(:commits) # => false
# current_controller?(:commits, :tree) # => true
def current_controller?(*args)
args.any? { |v| v.to_s.downcase == controller.controller_name }
end
# Check if a particular action is the current one
#
# args - One or more action names to check
#
# Examples
#
# # On Projects#new
# current_action?(:new) # => true
# current_action?(:create) # => false
# current_action?(:new, :create) # => true
def current_action?(*args)
args.any? { |v| v.to_s.downcase == action_name }
end
def date_from_to(from, to)
"#{from.to_s(:short)} - #{to.to_s(:short)}"
end
def body_data_page
path = controller.controller_path.split('/')
namespace = path.first if path.second
[namespace, controller.controller_name, controller.action_name].compact.join(":")
end
def duration_in_words(finished_at, started_at)
if finished_at && started_at
interval_in_seconds = finished_at.to_i - started_at.to_i
elsif started_at
interval_in_seconds = Time.now.to_i - started_at.to_i
end
time_interval_in_words(interval_in_seconds)
end
def time_interval_in_words(interval_in_seconds)
minutes = interval_in_seconds / 60
seconds = interval_in_seconds - minutes * 60
if minutes >= 1
"#{pluralize(minutes, "minute")} #{pluralize(seconds, "second")}"
else
"#{pluralize(seconds, "second")}"
end
end
end
end
module Ci
module BuildsHelper
def build_ref_link build
gitlab_ref_link build.project, build.ref
end
def build_compare_link build
gitlab_compare_link build.project, build.commit.short_before_sha, build.short_sha
end
def build_commit_link build
gitlab_commit_link build.project, build.short_sha
end
def build_url(build)
ci_project_build_url(build.project, build)
end
def build_status_alert_class(build)
if build.success?
'alert-success'
elsif build.failed?
'alert-danger'
elsif build.canceled?
'alert-disabled'
else
'alert-warning'
end
end
def build_icon_css_class(build)
if build.success?
'fa-circle cgreen'
elsif build.failed?
'fa-circle cred'
else
'fa-circle light'
end
end
end
end
module Ci
module CommitsHelper
def commit_status_alert_class(commit)
return 'alert-info' unless commit
case commit.status
when 'success'
'alert-success'
when 'failed', 'canceled'
'alert-danger'
when 'skipped'
'alert-disabled'
else
'alert-warning'
end
end
def ci_commit_path(commit)
ci_project_ref_commits_path(commit.project, commit.ref, commit.sha)
end
def commit_link(commit)
link_to(commit.short_sha, ci_commit_path(commit))
end
def truncate_first_line(message, length = 50)
truncate(message.each_line.first.chomp, length: length) if message
end
def ci_commit_title(commit)
content_tag :span do
link_to(
simple_sanitize(commit.project.name), ci_project_path(commit.project)
) + ' @ ' +
gitlab_commit_link(@project, @commit.sha)
end
end
end
end
module Ci
module GitlabHelper
def no_turbolink
{ :"data-no-turbolink" => "data-no-turbolink" }
end
def gitlab_ref_link project, ref
gitlab_url = project.gitlab_url.dup
gitlab_url << "/commits/#{ref}"
link_to ref, gitlab_url, no_turbolink
end
def gitlab_compare_link project, before, after
gitlab_url = project.gitlab_url.dup
gitlab_url << "/compare/#{before}...#{after}"
link_to "#{before}...#{after}", gitlab_url, no_turbolink
end
def gitlab_commit_link project, sha
gitlab_url = project.gitlab_url.dup
gitlab_url << "/commit/#{sha}"
link_to Ci::Commit.truncate_sha(sha), gitlab_url, no_turbolink
end
def yaml_web_editor_link(project)
commits = project.commits
if commits.any? && commits.last.push_data[:ci_yaml_file]
"#{@project.gitlab_url}/edit/master/.gitlab-ci.yml"
else
"#{@project.gitlab_url}/new/master"
end
end
end
end
module Ci
module IconsHelper
def boolean_to_icon(value)
if value.to_s == "true"
content_tag :i, nil, class: 'fa fa-circle cgreen'
else
content_tag :i, nil, class: 'fa fa-power-off clgray'
end
end
end
end
module Ci
module ProjectsHelper
def ref_tab_class ref = nil
'active' if ref == @ref
end
def success_ratio(success_builds, failed_builds)
failed_builds = failed_builds.count(:all)
success_builds = success_builds.count(:all)
return 100 if failed_builds.zero?
ratio = (success_builds.to_f / (success_builds + failed_builds)) * 100
ratio.to_i
end
def markdown_badge_code(project, ref)
url = status_ci_project_url(project, ref: ref, format: 'png')
"[![build status](#{url})](#{ci_project_url(project, ref: ref)})"
end
def html_badge_code(project, ref)
url = status_ci_project_url(project, ref: ref, format: 'png')
"<a href='#{ci_project_url(project, ref: ref)}'><img src='#{url}' /></a>"
end
def project_uses_specific_runner?(project)
project.runners.any?
end
def no_runners_for_project?(project)
project.runners.blank? &&
Ci::Runner.shared.blank?
end
end
end
module Ci
module RoutesHelper
class Base
include Gitlab::Application.routes.url_helpers
def default_url_options
{
host: Settings.gitlab['host'],
protocol: Settings.gitlab['https'] ? "https" : "http",
port: Settings.gitlab['port']
}
end
end
def url_helpers
@url_helpers ||= Base.new
end
def self.method_missing(method, *args, &block)
@url_helpers ||= Base.new
if @url_helpers.respond_to?(method)
@url_helpers.send(method, *args, &block)
else
super method, *args, &block
end
end
end
end
module Ci
module RunnersHelper
def runner_status_icon(runner)
unless runner.contacted_at
return content_tag :i, nil,
class: "fa fa-warning-sign",
title: "New runner. Has not connected yet"
end
status =
if runner.active?
runner.contacted_at > 3.hour.ago ? :online : :offline
else
:paused
end
content_tag :i, nil,
class: "fa fa-circle runner-status-#{status}",
title: "Runner is #{status}, last contact was #{time_ago_in_words(runner.contacted_at)} ago"
end
end
end
module Ci
module TriggersHelper
def ci_build_trigger_url(project_id, ref_name)
"#{Settings.gitlab_ci.url}/ci/api/v1/projects/#{project_id}/refs/#{ref_name}/trigger"
end
end
end
module Ci
module UserHelper
def user_avatar_url(user = nil, size = nil, default = 'identicon')
size = 40 if size.nil? || size <= 0
if user.blank? || user.avatar_url.blank?
'ci/no_avatar.png'
elsif /^(http(s?):\/\/(www|secure)\.gravatar\.com\/avatar\/(\w*))/ =~ user.avatar_url
Regexp.last_match[0] + "?s=#{size}&d=#{default}"
else
user.avatar_url
end
end
end
end
module Ci
module Emails
module Builds
def build_fail_email(build_id, to)
@build = Ci::Build.find(build_id)
@project = @build.project
mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha))
end
def build_success_email(build_id, to)
@build = Ci::Build.find(build_id)
@project = @build.project
mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha))
end
end
end
end
module Ci
class Notify < ActionMailer::Base
include Ci::Emails::Builds
add_template_helper Ci::ApplicationHelper
add_template_helper Ci::GitlabHelper
default_url_options[:host] = Gitlab.config.gitlab.host
default_url_options[:protocol] = Gitlab.config.gitlab.protocol
default_url_options[:port] = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port?
default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root
default from: Gitlab.config.gitlab.email_from
# Just send email with 3 seconds delay
def self.delay
delay_for(2.seconds)
end
private
# Formats arguments into a String suitable for use as an email subject
#
# extra - Extra Strings to be inserted into the subject
#
# Examples
#
# >> subject('Lorem ipsum')
# => "GitLab-CI | Lorem ipsum"
#
# # Automatically inserts Project name when @project is set
# >> @project = Project.last
# => #<Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
# >> subject('Lorem ipsum')
# => "GitLab-CI | Ruby on Rails | Lorem ipsum "
#
# # Accepts multiple arguments
# >> subject('Lorem ipsum', 'Dolor sit amet')
# => "GitLab-CI | Lorem ipsum | Dolor sit amet"
def subject(*extra)
subject = "GitLab-CI"
subject << (@project ? " | #{@project.name}" : "")
subject << " | " + extra.join(' | ') if extra.present?
subject
end
end
end
......@@ -100,7 +100,7 @@ class Notify < BaseMailer
def mail_thread(model, headers = {})
if @project
headers['X-GitLab-Project'] = @project.name
headers['X-GitLab-Project'] = @project.name
headers['X-GitLab-Project-Id'] = @project.id
headers['X-GitLab-Project-Path'] = @project.path_with_namespace
end
......
......@@ -149,6 +149,7 @@ class Ability
:admin_merge_request,
:create_merge_request,
:create_wiki,
:manage_builds,
:push_code
]
end
......
# == Schema Information
#
# Table name: application_settings
#
# id :integer not null, primary key
# all_broken_builds :boolean
# add_pusher :boolean
# created_at :datetime
# updated_at :datetime
#
module Ci
class ApplicationSetting < ActiveRecord::Base
extend Ci::Model
def self.current
Ci::ApplicationSetting.last
end
def self.create_from_defaults
create(
all_broken_builds: Settings.gitlab_ci['all_broken_builds'],
add_pusher: Settings.gitlab_ci['add_pusher'],
)
end
end
end
# == Schema Information
#
# Table name: builds
#
# id :integer not null, primary key
# project_id :integer
# status :string(255)
# finished_at :datetime
# trace :text
# created_at :datetime
# updated_at :datetime
# started_at :datetime
# runner_id :integer
# commit_id :integer
# coverage :float
# commands :text
# job_id :integer
# name :string(255)
# options :text
# allow_failure :boolean default(FALSE), not null
# stage :string(255)
# deploy :boolean default(FALSE)
# trigger_request_id :integer
#
module Ci
class Build < ActiveRecord::Base
extend Ci::Model
LAZY_ATTRIBUTES = ['trace']
belongs_to :commit, class_name: 'Ci::Commit'
belongs_to :project, class_name: 'Ci::Project'
belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
serialize :options
validates :commit, presence: true
validates :status, presence: true
validates :coverage, numericality: true, allow_blank: true
scope :running, ->() { where(status: "running") }
scope :pending, ->() { where(status: "pending") }
scope :success, ->() { where(status: "success") }
scope :failed, ->() { where(status: "failed") }
scope :unstarted, ->() { where(runner_id: nil) }
scope :running_or_pending, ->() { where(status:[:running, :pending]) }
acts_as_taggable
# To prevent db load megabytes of data from trace
default_scope -> { select(Ci::Build.columns_without_lazy) }
class << self
def columns_without_lazy
(column_names - LAZY_ATTRIBUTES).map do |column_name|
"#{table_name}.#{column_name}"
end
end
def last_month
where('created_at > ?', Date.today - 1.month)
end
def first_pending
pending.unstarted.order('created_at ASC').first
end
def create_from(build)
new_build = build.dup
new_build.status = :pending
new_build.runner_id = nil
new_build.save
end
def retry(build)
new_build = Ci::Build.new(status: :pending)
new_build.options = build.options
new_build.commands = build.commands
new_build.tag_list = build.tag_list
new_build.commit_id = build.commit_id
new_build.project_id = build.project_id
new_build.name = build.name
new_build.allow_failure = build.allow_failure
new_build.stage = build.stage
new_build.trigger_request = build.trigger_request
new_build.save
new_build
end
end
state_machine :status, initial: :pending do
event :run do
transition pending: :running
end
event :drop do
transition running: :failed
end
event :success do
transition running: :success
end
event :cancel do
transition [:pending, :running] => :canceled
end
after_transition pending: :running do |build, transition|
build.update_attributes started_at: Time.now
end
after_transition any => [:success, :failed, :canceled] do |build, transition|
build.update_attributes finished_at: Time.now
project = build.project
if project.web_hooks?
Ci::WebHookService.new.build_end(build)
end
if build.commit.success?
build.commit.create_next_builds(build.trigger_request)
end
project.execute_services(build)
if project.coverage_enabled?
build.update_coverage
end
end
state :pending, value: 'pending'
state :running, value: 'running'
state :failed, value: 'failed'
state :success, value: 'success'
state :canceled, value: 'canceled'
end
delegate :sha, :short_sha, :before_sha, :ref,
to: :commit, prefix: false
def trace_html
html = Ci::Ansi2html::convert(trace) if trace.present?
html ||= ''
end
def trace
if project && read_attribute(:trace).present?
read_attribute(:trace).gsub(project.token, 'xxxxxx')
end
end
def started?
!pending? && !canceled? && started_at
end
def active?
running? || pending?
end
def complete?
canceled? || success? || failed?
end
def ignored?
failed? && allow_failure?
end
def timeout
project.timeout
end
def variables
yaml_variables + project_variables + trigger_variables
end
def duration
if started_at && finished_at
finished_at - started_at
elsif started_at
Time.now - started_at
end
end
def project
commit.project
end
def project_id
commit.project_id
end
def project_name
project.name
end
def repo_url
project.repo_url_with_auth
end
def allow_git_fetch
project.allow_git_fetch
end
def update_coverage
coverage = extract_coverage(trace, project.coverage_regex)
if coverage.is_a? Numeric
update_attributes(coverage: coverage)
end
end
def extract_coverage(text, regex)
begin
matches = text.gsub(Regexp.new(regex)).to_a.last
coverage = matches.gsub(/\d+(\.\d+)?/).first
if coverage.present?
coverage.to_f
end
rescue => ex
# if bad regex or something goes wrong we dont want to interrupt transition
# so we just silentrly ignore error for now
end
end
def trace
if File.exist?(path_to_trace)
File.read(path_to_trace)
else
# backward compatibility
read_attribute :trace
end
end
def trace=(trace)
unless Dir.exists? dir_to_trace
FileUtils.mkdir_p dir_to_trace
end
File.write(path_to_trace, trace)
end
def dir_to_trace
File.join(
Settings.gitlab_ci.builds_path,
created_at.utc.strftime("%Y_%m"),
project.id.to_s
)
end
def path_to_trace
"#{dir_to_trace}/#{id}.log"
end
private
def yaml_variables
if commit.config_processor
commit.config_processor.variables.map do |key, value|
{ key: key, value: value, public: true }
end
else
[]
end
end
def project_variables
project.variables.map do |variable|
{ key: variable.key, value: variable.value, public: false }
end
end
def trigger_variables
if trigger_request && trigger_request.variables
trigger_request.variables.map do |key, value|
{ key: key, value: value, public: false }
end
else
[]
end
end
end
end
# == Schema Information
#
# Table name: commits
#
# id :integer not null, primary key
# project_id :integer
# ref :string(255)
# sha :string(255)
# before_sha :string(255)
# push_data :text
# created_at :datetime
# updated_at :datetime
# tag :boolean default(FALSE)
# yaml_errors :text
# committed_at :datetime
#
module Ci
class Commit < ActiveRecord::Base
extend Ci::Model
belongs_to :project, class_name: 'Ci::Project'
has_many :builds, dependent: :destroy, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
serialize :push_data
validates_presence_of :ref, :sha, :before_sha, :push_data
validate :valid_commit_sha
def self.truncate_sha(sha)
sha[0...8]
end
def to_param
sha
end
def last_build
builds.order(:id).last
end
def retry
builds_without_retry.each do |build|
Ci::Build.retry(build)
end
end
def valid_commit_sha
if self.sha == Ci::Git::BLANK_SHA
self.errors.add(:sha, " cant be 00000000 (branch removal)")
end
end
def new_branch?
before_sha == Ci::Git::BLANK_SHA
end
def compare?
!new_branch?
end
def git_author_name
commit_data[:author][:name] if commit_data && commit_data[:author]
end
def git_author_email
commit_data[:author][:email] if commit_data && commit_data[:author]
end
def git_commit_message
commit_data[:message] if commit_data && commit_data[:message]
end
def short_before_sha
Ci::Commit.truncate_sha(before_sha)
end
def short_sha
Ci::Commit.truncate_sha(sha)
end
def commit_data
push_data[:commits].find do |commit|
commit[:id] == sha
end
rescue
nil
end
def project_recipients
recipients = project.email_recipients.split(' ')
if project.email_add_pusher? && push_data[:user_email].present?
recipients << push_data[:user_email]
end
recipients.uniq
end
def stage
return unless config_processor
stages = builds_without_retry.select(&:active?).map(&:stage)
config_processor.stages.find { |stage| stages.include? stage }
end
def create_builds_for_stage(stage, trigger_request)
return if skip_ci? && trigger_request.blank?
return unless config_processor
builds_attrs = config_processor.builds_for_stage_and_ref(stage, ref, tag)
builds_attrs.map do |build_attrs|
builds.create!({
project: project,
name: build_attrs[:name],
commands: build_attrs[:script],
tag_list: build_attrs[:tags],
options: build_attrs[:options],
allow_failure: build_attrs[:allow_failure],
stage: build_attrs[:stage],
trigger_request: trigger_request,
})
end
end
def create_next_builds(trigger_request)
return if skip_ci? && trigger_request.blank?
return unless config_processor
stages = builds.where(trigger_request: trigger_request).group_by(&:stage)
config_processor.stages.any? do |stage|
!stages.include?(stage) && create_builds_for_stage(stage, trigger_request).present?
end
end
def create_builds(trigger_request = nil)
return if skip_ci? && trigger_request.blank?
return unless config_processor
config_processor.stages.any? do |stage|
create_builds_for_stage(stage, trigger_request).present?
end
end
def builds_without_retry
@builds_without_retry ||=
begin
grouped_builds = builds.group_by(&:name)
grouped_builds.map do |name, builds|
builds.sort_by(&:id).last
end
end
end
def builds_without_retry_sorted
return builds_without_retry unless config_processor
stages = config_processor.stages
builds_without_retry.sort_by do |build|
[stages.index(build.stage) || -1, build.name || ""]
end
end
def retried_builds
@retried_builds ||= (builds.order(id: :desc) - builds_without_retry)
end
def status
if skip_ci?
return 'skipped'
elsif yaml_errors.present?
return 'failed'
elsif builds.none?
return 'skipped'
elsif success?
'success'
elsif pending?
'pending'
elsif running?
'running'
elsif canceled?
'canceled'
else
'failed'
end
end
def pending?
builds_without_retry.all? do |build|
build.pending?
end
end
def running?
builds_without_retry.any? do |build|
build.running? || build.pending?
end
end
def success?
builds_without_retry.all? do |build|
build.success? || build.ignored?
end
end
def failed?
status == 'failed'
end
def canceled?
builds_without_retry.all? do |build|
build.canceled?
end
end
def duration
@duration ||= builds_without_retry.select(&:duration).sum(&:duration).to_i
end
def finished_at
@finished_at ||= builds.order('finished_at DESC').first.try(:finished_at)
end
def coverage
if project.coverage_enabled?
coverage_array = builds_without_retry.map(&:coverage).compact
if coverage_array.size >= 1
'%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
end
end
end
def matrix?
builds_without_retry.size > 1
end
def config_processor
@config_processor ||= Ci::GitlabCiYamlProcessor.new(push_data[:ci_yaml_file] || project.generated_yaml_config)
rescue Ci::GitlabCiYamlProcessor::ValidationError => e
save_yaml_error(e.message)
nil
rescue Exception => e
logger.error e.message + "\n" + e.backtrace.join("\n")
save_yaml_error("Undefined yaml error")
nil
end
def skip_ci?
return false if builds.any?
commits = push_data[:commits]
commits.present? && commits.last[:message] =~ /(\[ci skip\])/
end
def update_committed!
update!(committed_at: DateTime.now)
end
private
def save_yaml_error(error)
return if self.yaml_errors?
self.yaml_errors = error
save
end
end
end
# == Schema Information
#
# Table name: events
#
# id :integer not null, primary key
# project_id :integer
# user_id :integer
# is_admin :integer
# description :text
# created_at :datetime
# updated_at :datetime
#
module Ci
class Event < ActiveRecord::Base
extend Ci::Model
belongs_to :project, class_name: 'Ci::Project'
validates :description,
presence: true,
length: { in: 5..200 }
scope :admin, ->(){ where(is_admin: true) }
scope :project_wide, ->(){ where(is_admin: false) }
end
end
此差异已折叠。
module Ci
module ProjectStatus
def status
last_commit.status if last_commit
end
def broken?
last_commit.failed? if last_commit
end
def success?
last_commit.success? if last_commit
end
def broken_or_success?
broken? || success?
end
def last_commit
@last_commit ||= commits.last if commits.any?
end
def last_commit_date
last_commit.try(:created_at)
end
def human_status
status
end
# only check for toggling build status within same ref.
def last_commit_changed_status?
ref = last_commit.ref
last_commits = commits.where(ref: ref).last(2)
if last_commits.size < 2
false
else
last_commits[0].status != last_commits[1].status
end
end
def last_commit_for_ref(ref)
commits.where(ref: ref).last
end
end
end
此差异已折叠。
# == Schema Information
#
# Table name: runner_projects
#
# id :integer not null, primary key
# runner_id :integer not null
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
#
module Ci
class RunnerProject < ActiveRecord::Base
extend Ci::Model
belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :project, class_name: 'Ci::Project'
validates_uniqueness_of :runner_id, scope: :project_id
end
end
此差异已折叠。
# == Schema Information
#
# Table name: triggers
#
# id :integer not null, primary key
# token :string(255)
# project_id :integer not null
# deleted_at :datetime
# created_at :datetime
# updated_at :datetime
#
module Ci
class Trigger < ActiveRecord::Base
extend Ci::Model
acts_as_paranoid
belongs_to :project, class_name: 'Ci::Project'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
validates_presence_of :token
validates_uniqueness_of :token
before_validation :set_default_values
def set_default_values
self.token = SecureRandom.hex(15) if self.token.blank?
end
def last_trigger_request
trigger_requests.last
end
def short_token
token[0...10]
end
end
end
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
......@@ -19,7 +19,7 @@
#
class GitlabIssueTrackerService < IssueTrackerService
include Rails.application.routes.url_helpers
include Gitlab::Application.routes.url_helpers
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册