summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorDouwe Maan <douwe@gitlab.com>2015-10-07 15:15:35 +0200
committerDouwe Maan <douwe@gitlab.com>2015-10-07 15:15:35 +0200
commit4f629dab2a14c190b641bd709c28ebdad5b7a062 (patch)
tree00035d114b896a38b4a2a6ac835411ba36d3e1e5 /app
parent48837850838c8acb0c02ae60b29e18d287865878 (diff)
parent0611a18964a998b6edc81ef9b469f9f70430e542 (diff)
downloadgitlab-ce-4f629dab2a14c190b641bd709c28ebdad5b7a062.tar.gz
Merge branch 'master' into rs-redactor-filter
Diffstat (limited to 'app')
-rwxr-xr-xapp/assets/fonts/OFL.txt92
-rwxr-xr-xapp/assets/fonts/SourceSansPro-Black.ttfbin0 -> 148368 bytes
-rwxr-xr-xapp/assets/fonts/SourceSansPro-BlackItalic.ttfbin0 -> 116360 bytes
-rwxr-xr-xapp/assets/fonts/SourceSansPro-Bold.ttfbin0 -> 291424 bytes
-rwxr-xr-xapp/assets/fonts/SourceSansPro-BoldItalic.ttfbin0 -> 116192 bytes
-rwxr-xr-xapp/assets/fonts/SourceSansPro-ExtraLight.ttfbin0 -> 150528 bytes
-rwxr-xr-xapp/assets/fonts/SourceSansPro-ExtraLightItalic.ttfbin0 -> 117140 bytes
-rwxr-xr-xapp/assets/fonts/SourceSansPro-Italic.ttfbin0 -> 117328 bytes
-rwxr-xr-xapp/assets/fonts/SourceSansPro-Light.ttfbin0 -> 293220 bytes
-rwxr-xr-xapp/assets/fonts/SourceSansPro-LightItalic.ttfbin0 -> 116960 bytes
-rwxr-xr-xapp/assets/fonts/SourceSansPro-Regular.ttfbin0 -> 293956 bytes
-rwxr-xr-xapp/assets/fonts/SourceSansPro-Semibold.ttfbin0 -> 292404 bytes
-rwxr-xr-xapp/assets/fonts/SourceSansPro-SemiboldItalic.ttfbin0 -> 116424 bytes
-rw-r--r--app/assets/images/ci/arch.jpgbin0 -> 25222 bytes
-rw-r--r--app/assets/images/ci/favicon.icobin0 -> 5430 bytes
-rw-r--r--app/assets/images/ci/loader.gifbin0 -> 4405 bytes
-rw-r--r--app/assets/images/ci/no_avatar.pngbin0 -> 1337 bytes
-rw-r--r--app/assets/images/ci/rails.pngbin0 -> 6646 bytes
-rw-r--r--app/assets/images/ci/service_sample.pngbin0 -> 76024 bytes
-rw-r--r--app/assets/javascripts/activities.js.coffee4
-rw-r--r--app/assets/javascripts/blob/blob_file_dropzone.js.coffee67
-rw-r--r--app/assets/javascripts/ci/Chart.min.js39
-rw-r--r--app/assets/javascripts/ci/application.js.coffee40
-rw-r--r--app/assets/javascripts/ci/build.coffee41
-rw-r--r--app/assets/javascripts/ci/projects.js.coffee6
-rw-r--r--app/assets/javascripts/dropzone_input.js.coffee1
-rw-r--r--app/assets/javascripts/issuable_context.js.coffee9
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee7
-rw-r--r--app/assets/javascripts/merge_request_widget.js.coffee11
-rw-r--r--app/assets/javascripts/notes.js.coffee20
-rw-r--r--app/assets/javascripts/project.js.coffee16
-rw-r--r--app/assets/javascripts/syntax_highlight.coffee13
-rw-r--r--app/assets/javascripts/tree.js.coffee5
-rw-r--r--app/assets/javascripts/zen_mode.js.coffee2
-rw-r--r--app/assets/stylesheets/application.scss7
-rw-r--r--app/assets/stylesheets/base/fonts.scss25
-rw-r--r--app/assets/stylesheets/base/gl_bootstrap.scss26
-rw-r--r--app/assets/stylesheets/base/gl_variables.scss57
-rw-r--r--app/assets/stylesheets/base/layout.scss1
-rw-r--r--app/assets/stylesheets/base/mixins.scss140
-rw-r--r--app/assets/stylesheets/base/variables.scss30
-rw-r--r--app/assets/stylesheets/ci/builds.scss76
-rw-r--r--app/assets/stylesheets/ci/lint.scss10
-rw-r--r--app/assets/stylesheets/ci/projects.scss59
-rw-r--r--app/assets/stylesheets/ci/runners.scss36
-rw-r--r--app/assets/stylesheets/ci/status.scss37
-rw-r--r--app/assets/stylesheets/ci/xterm.scss906
-rw-r--r--app/assets/stylesheets/generic/avatar.scss9
-rw-r--r--app/assets/stylesheets/generic/blocks.scss49
-rw-r--r--app/assets/stylesheets/generic/buttons.scss156
-rw-r--r--app/assets/stylesheets/generic/callout.scss45
-rw-r--r--app/assets/stylesheets/generic/common.scss51
-rw-r--r--app/assets/stylesheets/generic/files.scss37
-rw-r--r--app/assets/stylesheets/generic/filters.scss25
-rw-r--r--app/assets/stylesheets/generic/header.scss32
-rw-r--r--app/assets/stylesheets/generic/issue_box.scss9
-rw-r--r--app/assets/stylesheets/generic/lists.scss36
-rw-r--r--app/assets/stylesheets/generic/markdown_area.scss11
-rw-r--r--app/assets/stylesheets/generic/mobile.scss17
-rw-r--r--app/assets/stylesheets/generic/pagination.scss34
-rw-r--r--app/assets/stylesheets/generic/selects.scss8
-rw-r--r--app/assets/stylesheets/generic/sidebar.scss80
-rw-r--r--app/assets/stylesheets/generic/timeline.scss140
-rw-r--r--app/assets/stylesheets/generic/typography.scss41
-rw-r--r--app/assets/stylesheets/generic/zen.scss4
-rw-r--r--app/assets/stylesheets/highlight/white.scss15
-rw-r--r--app/assets/stylesheets/pages/commit.scss57
-rw-r--r--app/assets/stylesheets/pages/commits.scss6
-rw-r--r--app/assets/stylesheets/pages/dashboard.scss6
-rw-r--r--app/assets/stylesheets/pages/diff.scss16
-rw-r--r--app/assets/stylesheets/pages/editor.scss18
-rw-r--r--app/assets/stylesheets/pages/events.scss86
-rw-r--r--app/assets/stylesheets/pages/groups.scss6
-rw-r--r--app/assets/stylesheets/pages/issuable.scss46
-rw-r--r--app/assets/stylesheets/pages/issues.scss14
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss39
-rw-r--r--app/assets/stylesheets/pages/milestone.scss4
-rw-r--r--app/assets/stylesheets/pages/note_form.scss18
-rw-r--r--app/assets/stylesheets/pages/notes.scss31
-rw-r--r--app/assets/stylesheets/pages/projects.scss336
-rw-r--r--app/assets/stylesheets/pages/tree.scss56
-rw-r--r--app/assets/stylesheets/themes/gitlab-theme.scss14
-rw-r--r--app/controllers/admin/application_settings_controller.rb1
-rw-r--r--app/controllers/admin/labels_controller.rb58
-rw-r--r--app/controllers/admin/users_controller.rb8
-rw-r--r--app/controllers/application_controller.rb17
-rw-r--r--app/controllers/autocomplete_controller.rb1
-rw-r--r--app/controllers/ci/admin/application_controller.rb10
-rw-r--r--app/controllers/ci/admin/application_settings_controller.rb31
-rw-r--r--app/controllers/ci/admin/builds_controller.rb18
-rw-r--r--app/controllers/ci/admin/events_controller.rb9
-rw-r--r--app/controllers/ci/admin/projects_controller.rb19
-rw-r--r--app/controllers/ci/admin/runner_projects_controller.rb34
-rw-r--r--app/controllers/ci/admin/runners_controller.rb72
-rw-r--r--app/controllers/ci/application_controller.rb74
-rw-r--r--app/controllers/ci/builds_controller.rb52
-rw-r--r--app/controllers/ci/commits_controller.rb32
-rw-r--r--app/controllers/ci/events_controller.rb21
-rw-r--r--app/controllers/ci/lints_controller.rb26
-rw-r--r--app/controllers/ci/projects_controller.rb56
-rw-r--r--app/controllers/ci/runner_projects_controller.rb36
-rw-r--r--app/controllers/ci/services_controller.rb59
-rw-r--r--app/controllers/dashboard/projects_controller.rb16
-rw-r--r--app/controllers/dashboard/snippets_controller.rb10
-rw-r--r--app/controllers/dashboard_controller.rb28
-rw-r--r--app/controllers/explore/application_controller.rb2
-rw-r--r--app/controllers/explore/groups_controller.rb3
-rw-r--r--app/controllers/explore/projects_controller.rb3
-rw-r--r--app/controllers/explore/snippets_controller.rb6
-rw-r--r--app/controllers/groups_controller.rb6
-rw-r--r--app/controllers/help_controller.rb7
-rw-r--r--app/controllers/import/fogbugz_controller.rb104
-rw-r--r--app/controllers/invites_controller.rb4
-rw-r--r--app/controllers/namespaces_controller.rb2
-rw-r--r--app/controllers/oauth/applications_controller.rb2
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb4
-rw-r--r--app/controllers/passwords_controller.rb61
-rw-r--r--app/controllers/profiles/preferences_controller.rb1
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb2
-rw-r--r--app/controllers/projects/application_controller.rb10
-rw-r--r--app/controllers/projects/blob_controller.rb28
-rw-r--r--app/controllers/projects/builds_controller.rb25
-rw-r--r--app/controllers/projects/ci_settings_controller.rb36
-rw-r--r--app/controllers/projects/ci_web_hooks_controller.rb45
-rw-r--r--app/controllers/projects/commit_controller.rb9
-rw-r--r--app/controllers/projects/compare_controller.rb10
-rw-r--r--app/controllers/projects/forks_controller.rb12
-rw-r--r--app/controllers/projects/graphs_controller.rb11
-rw-r--r--app/controllers/projects/merge_requests_controller.rb11
-rw-r--r--app/controllers/projects/milestones_controller.rb7
-rw-r--r--app/controllers/projects/project_members_controller.rb2
-rw-r--r--app/controllers/projects/raw_controller.rb5
-rw-r--r--app/controllers/projects/runners_controller.rb65
-rw-r--r--app/controllers/projects/services_controller.rb6
-rw-r--r--app/controllers/projects/triggers_controller.rb35
-rw-r--r--app/controllers/projects/variables_controller.rb25
-rw-r--r--app/controllers/projects/wikis_controller.rb3
-rw-r--r--app/controllers/projects_controller.rb10
-rw-r--r--app/controllers/root_controller.rb11
-rw-r--r--app/controllers/sessions_controller.rb4
-rw-r--r--app/controllers/snippets_controller.rb8
-rw-r--r--app/finders/issuable_finder.rb2
-rw-r--r--app/finders/trending_projects_finder.rb17
-rw-r--r--app/helpers/application_helper.rb14
-rw-r--r--app/helpers/auth_helper.rb8
-rw-r--r--app/helpers/builds_helper.rb13
-rw-r--r--app/helpers/ci/gitlab_helper.rb36
-rw-r--r--app/helpers/ci/projects_helper.rb36
-rw-r--r--app/helpers/ci_status_helper.rb45
-rw-r--r--app/helpers/commits_helper.rb2
-rw-r--r--app/helpers/diff_helper.rb23
-rw-r--r--app/helpers/events_helper.rb22
-rw-r--r--app/helpers/gitlab_markdown_helper.rb4
-rw-r--r--app/helpers/gitlab_routing_helper.rb16
-rw-r--r--app/helpers/graph_helper.rb5
-rw-r--r--app/helpers/groups_helper.rb11
-rw-r--r--app/helpers/merge_requests_helper.rb13
-rw-r--r--app/helpers/notifications_helper.rb45
-rw-r--r--app/helpers/page_layout_helper.rb24
-rw-r--r--app/helpers/preferences_helper.rb11
-rw-r--r--app/helpers/projects_helper.rb42
-rw-r--r--app/helpers/runners_helper.rb20
-rw-r--r--app/helpers/time_helper.rb27
-rw-r--r--app/helpers/triggers_helper.rb5
-rw-r--r--app/helpers/version_check_helper.rb2
-rw-r--r--app/helpers/wiki_helper.rb24
-rw-r--r--app/mailers/ci/emails/builds.rb17
-rw-r--r--app/mailers/ci/notify.rb46
-rw-r--r--app/mailers/emails/notes.rb6
-rw-r--r--app/mailers/emails/projects.rb3
-rw-r--r--app/mailers/notify.rb8
-rw-r--r--app/models/ability.rb1
-rw-r--r--app/models/abuse_report.rb16
-rw-r--r--app/models/application_setting.rb2
-rw-r--r--app/models/ci/application_setting.rb27
-rw-r--r--app/models/ci/build.rb309
-rw-r--r--app/models/ci/commit.rb246
-rw-r--r--app/models/ci/event.rb27
-rw-r--r--app/models/ci/project.rb215
-rw-r--r--app/models/ci/project_status.rb35
-rw-r--r--app/models/ci/runner.rb84
-rw-r--r--app/models/ci/runner_project.rb21
-rw-r--r--app/models/ci/service.rb105
-rw-r--r--app/models/ci/trigger.rb39
-rw-r--r--app/models/ci/trigger_request.rb23
-rw-r--r--app/models/ci/variable.rb25
-rw-r--r--app/models/ci/web_hook.rb44
-rw-r--r--app/models/concerns/issuable.rb6
-rw-r--r--app/models/event.rb12
-rw-r--r--app/models/hooks/web_hook.rb2
-rw-r--r--app/models/issue.rb27
-rw-r--r--app/models/label.rb8
-rw-r--r--app/models/merge_request.rb15
-rw-r--r--app/models/merge_request_diff.rb12
-rw-r--r--app/models/milestone.rb2
-rw-r--r--app/models/namespace.rb4
-rw-r--r--app/models/note.rb3
-rw-r--r--app/models/notification.rb21
-rw-r--r--app/models/project.rb85
-rw-r--r--app/models/project_services/buildkite_service.rb8
-rw-r--r--app/models/project_services/ci/hip_chat_message.rb73
-rw-r--r--app/models/project_services/ci/hip_chat_service.rb93
-rw-r--r--app/models/project_services/ci/mail_service.rb84
-rw-r--r--app/models/project_services/ci/slack_message.rb92
-rw-r--r--app/models/project_services/ci/slack_service.rb81
-rw-r--r--app/models/project_services/ci_service.rb37
-rw-r--r--app/models/project_services/drone_ci_service.rb176
-rw-r--r--app/models/project_services/gitlab_ci_service.rb115
-rw-r--r--app/models/project_services/gitlab_issue_tracker_service.rb2
-rw-r--r--app/models/project_services/hipchat_service.rb25
-rw-r--r--app/models/project_services/jira_service.rb2
-rw-r--r--app/models/project_services/slack_service.rb6
-rw-r--r--app/models/project_team.rb4
-rw-r--r--app/models/repository.rb2
-rw-r--r--app/models/sent_notification.rb33
-rw-r--r--app/models/service.rb1
-rw-r--r--app/models/user.rb137
-rw-r--r--app/services/ci/create_builds_service.rb27
-rw-r--r--app/services/ci/create_commit_service.rb26
-rw-r--r--app/services/ci/create_trigger_request_service.rb22
-rw-r--r--app/services/ci/event_service.rb31
-rw-r--r--app/services/ci/image_for_build_service.rb31
-rw-r--r--app/services/ci/register_build_service.rb40
-rw-r--r--app/services/ci/test_hook_service.rb7
-rw-r--r--app/services/ci/web_hook_service.rb35
-rw-r--r--app/services/compare_service.rb5
-rw-r--r--app/services/event_create_service.rb4
-rw-r--r--app/services/files/base_service.rb2
-rw-r--r--app/services/files/create_service.rb4
-rw-r--r--app/services/git_push_service.rb12
-rw-r--r--app/services/merge_requests/merge_service.rb4
-rw-r--r--app/services/milestones/destroy_service.rb27
-rw-r--r--app/services/notification_service.rb4
-rw-r--r--app/services/projects/create_service.rb8
-rw-r--r--app/services/projects/download_service.rb43
-rw-r--r--app/services/projects/fork_service.rb8
-rw-r--r--app/services/projects/transfer_service.rb2
-rw-r--r--app/views/admin/application_settings/_form.html.haml5
-rw-r--r--app/views/admin/applications/index.html.haml2
-rw-r--r--app/views/admin/dashboard/index.html.haml2
-rw-r--r--app/views/admin/labels/_form.html.haml35
-rw-r--r--app/views/admin/labels/_label.html.haml5
-rw-r--r--app/views/admin/labels/destroy.js.haml2
-rw-r--r--app/views/admin/labels/edit.html.haml9
-rw-r--r--app/views/admin/labels/index.html.haml16
-rw-r--r--app/views/admin/labels/new.html.haml7
-rw-r--r--app/views/admin/users/_head.html.haml2
-rw-r--r--app/views/admin/users/index.html.haml2
-rw-r--r--app/views/ci/admin/application_settings/_form.html.haml24
-rw-r--r--app/views/ci/admin/application_settings/show.html.haml3
-rw-r--r--app/views/ci/admin/builds/_build.html.haml34
-rw-r--r--app/views/ci/admin/builds/index.html.haml28
-rw-r--r--app/views/ci/admin/events/index.html.haml17
-rw-r--r--app/views/ci/admin/projects/_project.html.haml29
-rw-r--r--app/views/ci/admin/projects/index.html.haml15
-rw-r--r--app/views/ci/admin/runner_projects/index.html.haml57
-rw-r--r--app/views/ci/admin/runners/_runner.html.haml48
-rw-r--r--app/views/ci/admin/runners/index.html.haml52
-rw-r--r--app/views/ci/admin/runners/show.html.haml124
-rw-r--r--app/views/ci/admin/runners/update.js.haml2
-rw-r--r--app/views/ci/commits/_commit.html.haml33
-rw-r--r--app/views/ci/errors/show.haml2
-rw-r--r--app/views/ci/events/index.html.haml19
-rw-r--r--app/views/ci/lints/_create.html.haml39
-rw-r--r--app/views/ci/lints/create.js.haml2
-rw-r--r--app/views/ci/lints/show.html.haml25
-rw-r--r--app/views/ci/notify/build_fail_email.html.haml19
-rw-r--r--app/views/ci/notify/build_fail_email.text.erb9
-rw-r--r--app/views/ci/notify/build_success_email.html.haml20
-rw-r--r--app/views/ci/notify/build_success_email.text.erb9
-rw-r--r--app/views/ci/projects/_info.html.haml2
-rw-r--r--app/views/ci/projects/_no_runners.html.haml8
-rw-r--r--app/views/ci/projects/show.html.haml60
-rw-r--r--app/views/ci/services/_form.html.haml57
-rw-r--r--app/views/ci/services/edit.html.haml1
-rw-r--r--app/views/ci/services/index.html.haml22
-rw-r--r--app/views/ci/shared/_guide.html.haml15
-rw-r--r--app/views/ci/shared/_no_runners.html.haml7
-rw-r--r--app/views/ci/user_sessions/new.html.haml8
-rw-r--r--app/views/dashboard/_activities.html.haml6
-rw-r--r--app/views/dashboard/_activity_head.html.haml7
-rw-r--r--app/views/dashboard/_groups_head.html.haml6
-rw-r--r--app/views/dashboard/_projects_head.html.haml8
-rw-r--r--app/views/dashboard/_snippets_head.html.haml7
-rw-r--r--app/views/dashboard/activity.html.haml7
-rw-r--r--app/views/dashboard/groups/index.html.haml11
-rw-r--r--app/views/dashboard/issues.html.haml12
-rw-r--r--app/views/dashboard/merge_requests.html.haml7
-rw-r--r--app/views/dashboard/milestones/_milestone.html.haml24
-rw-r--r--app/views/dashboard/milestones/index.html.haml28
-rw-r--r--app/views/dashboard/projects/_projects.html.haml (renamed from app/views/dashboard/_projects.html.haml)4
-rw-r--r--app/views/dashboard/projects/_zero_authorized_projects.html.haml (renamed from app/views/dashboard/_zero_authorized_projects.html.haml)0
-rw-r--r--app/views/dashboard/projects/index.atom.builder (renamed from app/views/dashboard/show.atom.builder)6
-rw-r--r--app/views/dashboard/projects/index.html.haml (renamed from app/views/dashboard/show.html.haml)5
-rw-r--r--app/views/dashboard/projects/starred.html.haml23
-rw-r--r--app/views/dashboard/snippets/index.html.haml38
-rw-r--r--app/views/devise/passwords/new.html.haml2
-rw-r--r--app/views/devise/sessions/_new_crowd.html.haml9
-rw-r--r--app/views/devise/shared/_signin_box.html.haml10
-rw-r--r--app/views/events/_commit.html.haml2
-rw-r--r--app/views/events/_event.html.haml4
-rw-r--r--app/views/events/_event_last_push.html.haml24
-rw-r--r--app/views/events/event/_common.html.haml5
-rw-r--r--app/views/events/event/_note.html.haml3
-rw-r--r--app/views/explore/_head.html.haml6
-rw-r--r--app/views/explore/groups/index.html.haml13
-rw-r--r--app/views/explore/projects/_filter.html.haml2
-rw-r--r--app/views/explore/projects/index.html.haml10
-rw-r--r--app/views/explore/projects/starred.html.haml14
-rw-r--r--app/views/explore/projects/trending.html.haml20
-rw-r--r--app/views/explore/snippets/index.html.haml18
-rw-r--r--app/views/groups/_projects.html.haml4
-rw-r--r--app/views/groups/edit.html.haml3
-rw-r--r--app/views/groups/group_members/index.html.haml4
-rw-r--r--app/views/groups/issues.html.haml25
-rw-r--r--app/views/groups/merge_requests.html.haml23
-rw-r--r--app/views/groups/milestones/_header_title.html.haml1
-rw-r--r--app/views/groups/milestones/_milestone.html.haml36
-rw-r--r--app/views/groups/milestones/index.html.haml26
-rw-r--r--app/views/groups/milestones/show.html.haml2
-rw-r--r--app/views/groups/projects.html.haml2
-rw-r--r--app/views/groups/show.html.haml30
-rw-r--r--app/views/help/index.html.haml39
-rw-r--r--app/views/help/show.html.haml2
-rw-r--r--app/views/import/fogbugz/new.html.haml25
-rw-r--r--app/views/import/fogbugz/new_user_map.html.haml49
-rw-r--r--app/views/import/fogbugz/status.html.haml51
-rw-r--r--app/views/kaminari/gitlab/_first_page.html.haml2
-rw-r--r--app/views/kaminari/gitlab/_last_page.html.haml2
-rw-r--r--app/views/kaminari/gitlab/_paginator.html.haml11
-rw-r--r--app/views/layouts/_head.html.haml1
-rw-r--r--app/views/layouts/_init_auto_complete.html.haml3
-rw-r--r--app/views/layouts/_page.html.haml10
-rw-r--r--app/views/layouts/ci/_info.html.haml2
-rw-r--r--app/views/layouts/ci/_nav_admin.html.haml33
-rw-r--r--app/views/layouts/ci/_nav_project.html.haml23
-rw-r--r--app/views/layouts/ci/_page.html.haml27
-rw-r--r--app/views/layouts/ci/admin.html.haml11
-rw-r--r--app/views/layouts/ci/application.html.haml11
-rw-r--r--app/views/layouts/ci/notify.html.haml19
-rw-r--r--app/views/layouts/ci/project.html.haml11
-rw-r--r--app/views/layouts/dashboard.html.haml2
-rw-r--r--app/views/layouts/devise.html.haml2
-rw-r--r--app/views/layouts/explore.html.haml5
-rw-r--r--app/views/layouts/group.html.haml4
-rw-r--r--app/views/layouts/group_settings.html.haml1
-rw-r--r--app/views/layouts/header/_default.html.haml9
-rw-r--r--app/views/layouts/header/_public.html.haml4
-rw-r--r--app/views/layouts/nav/_admin.html.haml13
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml46
-rw-r--r--app/views/layouts/nav/_explore.html.haml21
-rw-r--r--app/views/layouts/nav/_group.html.haml61
-rw-r--r--app/views/layouts/nav/_profile.html.haml4
-rw-r--r--app/views/layouts/nav/_project.html.haml19
-rw-r--r--app/views/layouts/nav/_project_settings.html.haml26
-rw-r--r--app/views/layouts/notify.html.haml2
-rw-r--r--app/views/layouts/profile.html.haml2
-rw-r--r--app/views/layouts/project.html.haml9
-rw-r--r--app/views/layouts/project_settings.html.haml1
-rw-r--r--app/views/layouts/snippets.html.haml7
-rw-r--r--app/views/notify/merged_merge_request_email.text.haml2
-rw-r--r--app/views/notify/new_user_email.html.haml2
-rw-r--r--app/views/notify/project_was_moved_email.html.haml2
-rw-r--r--app/views/notify/project_was_moved_email.text.erb2
-rw-r--r--app/views/profiles/accounts/show.html.haml10
-rw-r--r--app/views/profiles/applications.html.haml9
-rw-r--r--app/views/profiles/audit_log.html.haml9
-rw-r--r--app/views/profiles/emails/index.html.haml10
-rw-r--r--app/views/profiles/keys/index.html.haml13
-rw-r--r--app/views/profiles/notifications/show.html.haml10
-rw-r--r--app/views/profiles/passwords/edit.html.haml10
-rw-r--r--app/views/profiles/passwords/new.html.haml2
-rw-r--r--app/views/profiles/preferences/show.html.haml15
-rw-r--r--app/views/profiles/preferences/update.js.erb7
-rw-r--r--app/views/profiles/show.html.haml9
-rw-r--r--app/views/projects/_activity.html.haml3
-rw-r--r--app/views/projects/_home_panel.html.haml38
-rw-r--r--app/views/projects/_last_push.html.haml23
-rw-r--r--app/views/projects/_md_preview.html.haml4
-rw-r--r--app/views/projects/_readme.html.haml2
-rw-r--r--app/views/projects/activity.html.haml3
-rw-r--r--app/views/projects/blame/show.html.haml2
-rw-r--r--app/views/projects/blob/_actions.html.haml6
-rw-r--r--app/views/projects/blob/_blob.html.haml2
-rw-r--r--app/views/projects/blob/_header_title.html.haml1
-rw-r--r--app/views/projects/blob/_upload.html.haml28
-rw-r--r--app/views/projects/blob/edit.html.haml4
-rw-r--r--app/views/projects/blob/new.html.haml13
-rw-r--r--app/views/projects/blob/show.html.haml6
-rw-r--r--app/views/projects/branches/_branch.html.haml13
-rw-r--r--app/views/projects/branches/_commit.html.haml7
-rw-r--r--app/views/projects/branches/index.html.haml9
-rw-r--r--app/views/projects/branches/new.html.haml2
-rw-r--r--app/views/projects/builds/_build.html.haml50
-rw-r--r--app/views/projects/builds/show.html.haml159
-rw-r--r--app/views/projects/buttons/_dropdown.html.haml2
-rw-r--r--app/views/projects/buttons/_fork.html.haml1
-rw-r--r--app/views/projects/buttons/_notifications.html.haml14
-rw-r--r--app/views/projects/buttons/_star.html.haml5
-rw-r--r--app/views/projects/ci_settings/_form.html.haml103
-rw-r--r--app/views/projects/ci_settings/edit.html.haml21
-rw-r--r--app/views/projects/ci_web_hooks/index.html.haml92
-rw-r--r--app/views/projects/commit/_ci_menu.html.haml7
-rw-r--r--app/views/projects/commit/_commit_box.html.haml9
-rw-r--r--app/views/projects/commit/ci.html.haml62
-rw-r--r--app/views/projects/commit/show.html.haml2
-rw-r--r--app/views/projects/commits/_commit.html.haml10
-rw-r--r--app/views/projects/commits/_head.html.haml10
-rw-r--r--app/views/projects/commits/_header_title.html.haml1
-rw-r--r--app/views/projects/commits/show.html.haml26
-rw-r--r--app/views/projects/compare/_form.html.haml2
-rw-r--r--app/views/projects/compare/index.html.haml8
-rw-r--r--app/views/projects/compare/show.html.haml13
-rw-r--r--app/views/projects/diffs/_diffs.html.haml17
-rw-r--r--app/views/projects/diffs/_file.html.haml22
-rw-r--r--app/views/projects/diffs/_stats.html.haml32
-rw-r--r--app/views/projects/edit.html.haml7
-rw-r--r--app/views/projects/empty.html.haml76
-rw-r--r--app/views/projects/forks/new.html.haml21
-rw-r--r--app/views/projects/graphs/_head.html.haml4
-rw-r--r--app/views/projects/graphs/_header_title.html.haml1
-rw-r--r--app/views/projects/graphs/ci.html.haml7
-rw-r--r--app/views/projects/graphs/ci/_build_times.haml21
-rw-r--r--app/views/projects/graphs/ci/_builds.haml41
-rw-r--r--app/views/projects/graphs/ci/_overall.haml22
-rw-r--r--app/views/projects/graphs/commits.html.haml79
-rw-r--r--app/views/projects/graphs/show.html.haml3
-rw-r--r--app/views/projects/imports/show.html.haml8
-rw-r--r--app/views/projects/issues/_discussion.html.haml23
-rw-r--r--app/views/projects/issues/_header_title.html.haml1
-rw-r--r--app/views/projects/issues/_issue.html.haml2
-rw-r--r--app/views/projects/issues/_issues.html.haml11
-rw-r--r--app/views/projects/issues/index.html.haml6
-rw-r--r--app/views/projects/issues/new.html.haml2
-rw-r--r--app/views/projects/issues/show.html.haml34
-rw-r--r--app/views/projects/labels/_header_title.html.haml1
-rw-r--r--app/views/projects/labels/edit.html.haml2
-rw-r--r--app/views/projects/labels/index.html.haml16
-rw-r--r--app/views/projects/labels/new.html.haml2
-rw-r--r--app/views/projects/merge_requests/_discussion.html.haml17
-rw-r--r--app/views/projects/merge_requests/_header_title.html.haml1
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml7
-rw-r--r--app/views/projects/merge_requests/_merge_requests.html.haml11
-rw-r--r--app/views/projects/merge_requests/_new_compare.html.haml2
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml9
-rw-r--r--app/views/projects/merge_requests/_show.html.haml11
-rw-r--r--app/views/projects/merge_requests/edit.html.haml2
-rw-r--r--app/views/projects/merge_requests/index.html.haml6
-rw-r--r--app/views/projects/merge_requests/invalid.html.haml2
-rw-r--r--app/views/projects/merge_requests/new.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_how_to_merge.html.haml16
-rw-r--r--app/views/projects/merge_requests/show/_mr_box.html.haml21
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml9
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml57
-rw-r--r--app/views/projects/merge_requests/widget/_merged.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/open/_accept.html.haml2
-rw-r--r--app/views/projects/milestones/_form.html.haml2
-rw-r--r--app/views/projects/milestones/_header_title.html.haml1
-rw-r--r--app/views/projects/milestones/_milestone.html.haml42
-rw-r--r--app/views/projects/milestones/edit.html.haml1
-rw-r--r--app/views/projects/milestones/index.html.haml26
-rw-r--r--app/views/projects/milestones/new.html.haml1
-rw-r--r--app/views/projects/milestones/show.html.haml2
-rw-r--r--app/views/projects/network/show.html.haml1
-rw-r--r--app/views/projects/network/show.json.erb2
-rw-r--r--app/views/projects/new.html.haml5
-rw-r--r--app/views/projects/notes/_edit_form.html.haml2
-rw-r--r--app/views/projects/notes/_form.html.haml2
-rw-r--r--app/views/projects/notes/_note.html.haml21
-rw-r--r--app/views/projects/project_members/_header_title.html.haml1
-rw-r--r--app/views/projects/project_members/import.html.haml2
-rw-r--r--app/views/projects/project_members/index.html.haml45
-rw-r--r--app/views/projects/runners/_runner.html.haml34
-rw-r--r--app/views/projects/runners/_shared_runners.html.haml23
-rw-r--r--app/views/projects/runners/_specific_runners.html.haml29
-rw-r--r--app/views/projects/runners/edit.html.haml27
-rw-r--r--app/views/projects/runners/index.html.haml25
-rw-r--r--app/views/projects/runners/show.html.haml64
-rw-r--r--app/views/projects/show.html.haml27
-rw-r--r--app/views/projects/snippets/_header_title.html.haml1
-rw-r--r--app/views/projects/snippets/edit.html.haml2
-rw-r--r--app/views/projects/snippets/index.html.haml2
-rw-r--r--app/views/projects/snippets/new.html.haml2
-rw-r--r--app/views/projects/snippets/show.html.haml2
-rw-r--r--app/views/projects/tags/_tag.html.haml12
-rw-r--r--app/views/projects/tags/index.html.haml12
-rw-r--r--app/views/projects/tags/new.html.haml2
-rw-r--r--app/views/projects/tree/_tree.html.haml2
-rw-r--r--app/views/projects/tree/show.html.haml1
-rw-r--r--app/views/projects/triggers/_trigger.html.haml14
-rw-r--r--app/views/projects/triggers/index.html.haml67
-rw-r--r--app/views/projects/variables/show.html.haml39
-rw-r--r--app/views/projects/wikis/_form.html.haml2
-rw-r--r--app/views/projects/wikis/_header_title.html.haml1
-rw-r--r--app/views/projects/wikis/_main_links.html.haml11
-rw-r--r--app/views/projects/wikis/_nav.html.haml15
-rw-r--r--app/views/projects/wikis/edit.html.haml2
-rw-r--r--app/views/projects/wikis/empty.html.haml2
-rw-r--r--app/views/projects/wikis/git_access.html.haml21
-rw-r--r--app/views/projects/wikis/history.html.haml11
-rw-r--r--app/views/projects/wikis/pages.html.haml9
-rw-r--r--app/views/projects/wikis/show.html.haml25
-rw-r--r--app/views/shared/_clone_panel.html.haml8
-rw-r--r--app/views/shared/_event_filter.html.haml2
-rw-r--r--app/views/shared/_field.html.haml7
-rw-r--r--app/views/shared/_milestones_filter.html.haml7
-rw-r--r--app/views/shared/groups/_group.html.haml8
-rw-r--r--app/views/shared/issuable/_filter.html.haml9
-rw-r--r--app/views/shared/issuable/_form.html.haml4
-rw-r--r--app/views/shared/projects/_list.html.haml5
-rw-r--r--app/views/shared/projects/_project.html.haml18
-rw-r--r--app/views/snippets/_head.html.haml7
-rw-r--r--app/views/snippets/current_user_index.html.haml35
-rw-r--r--app/views/snippets/index.html.haml16
-rw-r--r--app/views/snippets/show.html.haml4
-rw-r--r--app/views/snippets/user_index.html.haml13
-rw-r--r--app/views/users/show.html.haml17
-rw-r--r--app/workers/ci/hip_chat_notifier_worker.rb19
-rw-r--r--app/workers/ci/slack_notifier_worker.rb10
-rw-r--r--app/workers/ci/web_hook_worker.rb9
-rw-r--r--app/workers/email_receiver_worker.rb2
-rw-r--r--app/workers/fork_registration_worker.rb12
-rw-r--r--app/workers/repository_fork_worker.rb34
-rw-r--r--app/workers/repository_import_worker.rb35
524 files changed, 10263 insertions, 2001 deletions
diff --git a/app/assets/fonts/OFL.txt b/app/assets/fonts/OFL.txt
new file mode 100755
index 00000000000..a9b845ed1d4
--- /dev/null
+++ b/app/assets/fonts/OFL.txt
@@ -0,0 +1,92 @@
+Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/app/assets/fonts/SourceSansPro-Black.ttf b/app/assets/fonts/SourceSansPro-Black.ttf
new file mode 100755
index 00000000000..cb89a2d171e
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-Black.ttf
Binary files differ
diff --git a/app/assets/fonts/SourceSansPro-BlackItalic.ttf b/app/assets/fonts/SourceSansPro-BlackItalic.ttf
new file mode 100755
index 00000000000..c719243c0d6
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-BlackItalic.ttf
Binary files differ
diff --git a/app/assets/fonts/SourceSansPro-Bold.ttf b/app/assets/fonts/SourceSansPro-Bold.ttf
new file mode 100755
index 00000000000..5d65c93242f
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-Bold.ttf
Binary files differ
diff --git a/app/assets/fonts/SourceSansPro-BoldItalic.ttf b/app/assets/fonts/SourceSansPro-BoldItalic.ttf
new file mode 100755
index 00000000000..d20dd0c5eca
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-BoldItalic.ttf
Binary files differ
diff --git a/app/assets/fonts/SourceSansPro-ExtraLight.ttf b/app/assets/fonts/SourceSansPro-ExtraLight.ttf
new file mode 100755
index 00000000000..bb4176c6fff
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-ExtraLight.ttf
Binary files differ
diff --git a/app/assets/fonts/SourceSansPro-ExtraLightItalic.ttf b/app/assets/fonts/SourceSansPro-ExtraLightItalic.ttf
new file mode 100755
index 00000000000..2c34f3b8dc4
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-ExtraLightItalic.ttf
Binary files differ
diff --git a/app/assets/fonts/SourceSansPro-Italic.ttf b/app/assets/fonts/SourceSansPro-Italic.ttf
new file mode 100755
index 00000000000..e5a1a86e631
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-Italic.ttf
Binary files differ
diff --git a/app/assets/fonts/SourceSansPro-Light.ttf b/app/assets/fonts/SourceSansPro-Light.ttf
new file mode 100755
index 00000000000..83a0a336661
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-Light.ttf
Binary files differ
diff --git a/app/assets/fonts/SourceSansPro-LightItalic.ttf b/app/assets/fonts/SourceSansPro-LightItalic.ttf
new file mode 100755
index 00000000000..88a6778d24f
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-LightItalic.ttf
Binary files differ
diff --git a/app/assets/fonts/SourceSansPro-Regular.ttf b/app/assets/fonts/SourceSansPro-Regular.ttf
new file mode 100755
index 00000000000..44486cdc670
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-Regular.ttf
Binary files differ
diff --git a/app/assets/fonts/SourceSansPro-Semibold.ttf b/app/assets/fonts/SourceSansPro-Semibold.ttf
new file mode 100755
index 00000000000..86b00c067e0
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-Semibold.ttf
Binary files differ
diff --git a/app/assets/fonts/SourceSansPro-SemiboldItalic.ttf b/app/assets/fonts/SourceSansPro-SemiboldItalic.ttf
new file mode 100755
index 00000000000..2c5ad3008c3
--- /dev/null
+++ b/app/assets/fonts/SourceSansPro-SemiboldItalic.ttf
Binary files differ
diff --git a/app/assets/images/ci/arch.jpg b/app/assets/images/ci/arch.jpg
new file mode 100644
index 00000000000..0e05674e840
--- /dev/null
+++ b/app/assets/images/ci/arch.jpg
Binary files differ
diff --git a/app/assets/images/ci/favicon.ico b/app/assets/images/ci/favicon.ico
new file mode 100644
index 00000000000..9663d4d00b9
--- /dev/null
+++ b/app/assets/images/ci/favicon.ico
Binary files differ
diff --git a/app/assets/images/ci/loader.gif b/app/assets/images/ci/loader.gif
new file mode 100644
index 00000000000..2fcb8f2da0d
--- /dev/null
+++ b/app/assets/images/ci/loader.gif
Binary files differ
diff --git a/app/assets/images/ci/no_avatar.png b/app/assets/images/ci/no_avatar.png
new file mode 100644
index 00000000000..752d26adba7
--- /dev/null
+++ b/app/assets/images/ci/no_avatar.png
Binary files differ
diff --git a/app/assets/images/ci/rails.png b/app/assets/images/ci/rails.png
new file mode 100644
index 00000000000..d5edc04e65f
--- /dev/null
+++ b/app/assets/images/ci/rails.png
Binary files differ
diff --git a/app/assets/images/ci/service_sample.png b/app/assets/images/ci/service_sample.png
new file mode 100644
index 00000000000..65d29e3fd89
--- /dev/null
+++ b/app/assets/images/ci/service_sample.png
Binary files differ
diff --git a/app/assets/javascripts/activities.js.coffee b/app/assets/javascripts/activities.js.coffee
index 777c62dc1b7..63803747413 100644
--- a/app/assets/javascripts/activities.js.coffee
+++ b/app/assets/javascripts/activities.js.coffee
@@ -1,7 +1,7 @@
class @Activities
constructor: ->
Pager.init 20, true
- $(".event_filter_link").bind "click", (event) =>
+ $(".event-filter .btn").bind "click", (event) =>
event.preventDefault()
@toggleFilter($(event.currentTarget))
@reloadActivities()
@@ -12,7 +12,7 @@ class @Activities
toggleFilter: (sender) ->
- sender.parent().toggleClass "active"
+ sender.toggleClass "active"
event_filters = $.cookie("event_filter")
filter = sender.attr("id").split("_")[0]
if event_filters
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js.coffee b/app/assets/javascripts/blob/blob_file_dropzone.js.coffee
new file mode 100644
index 00000000000..3ab3ba66754
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js.coffee
@@ -0,0 +1,67 @@
+class @BlobFileDropzone
+ constructor: (form, method) ->
+ form_dropzone = form.find('.dropzone')
+ Dropzone.autoDiscover = false
+ dropzone = form_dropzone.dropzone(
+ autoDiscover: false
+ autoProcessQueue: false
+ url: form.attr('action')
+ # Rails uses a hidden input field for PUT
+ # http://stackoverflow.com/questions/21056482/how-to-set-method-put-in-form-tag-in-rails
+ method: method
+ clickable: true
+ uploadMultiple: false
+ paramName: "file"
+ maxFilesize: gon.max_file_size or 10
+ parallelUploads: 1
+ maxFiles: 1
+ addRemoveLinks: true
+ previewsContainer: '.dropzone-previews'
+ headers:
+ "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
+
+ init: ->
+ this.on 'addedfile', (file) ->
+ $('.dropzone-alerts').html('').hide()
+ commit_message = form.find('#commit_message')[0]
+
+ if /^Upload/.test(commit_message.placeholder)
+ commit_message.placeholder = 'Upload ' + file.name
+
+ return
+
+ this.on 'removedfile', (file) ->
+ commit_message = form.find('#commit_message')[0]
+
+ if /^Upload/.test(commit_message.placeholder)
+ commit_message.placeholder = 'Upload new file'
+
+ return
+
+ this.on 'success', (header, response) ->
+ window.location.href = response.filePath
+ return
+
+ this.on 'maxfilesexceeded', (file) ->
+ @removeFile file
+ return
+
+ this.on 'sending', (file, xhr, formData) ->
+ formData.append('commit_message', form.find('#commit_message').val())
+ return
+
+ # Override behavior of adding error underneath preview
+ error: (file, errorMessage) ->
+ stripped = $("<div/>").html(errorMessage).text();
+ $('.dropzone-alerts').html('Error uploading file: \"' + stripped + '\"').show()
+ @removeFile file
+ return
+ )
+
+ submitButton = form.find('#submit-all')[0]
+ submitButton.addEventListener 'click', (e) ->
+ e.preventDefault()
+ e.stopPropagation()
+ alert "Please select a file" if dropzone[0].dropzone.getQueuedFiles().length == 0
+ dropzone[0].dropzone.processQueue()
+ return false
diff --git a/app/assets/javascripts/ci/Chart.min.js b/app/assets/javascripts/ci/Chart.min.js
new file mode 100644
index 00000000000..ab635881087
--- /dev/null
+++ b/app/assets/javascripts/ci/Chart.min.js
@@ -0,0 +1,39 @@
+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
diff --git a/app/assets/javascripts/ci/application.js.coffee b/app/assets/javascripts/ci/application.js.coffee
new file mode 100644
index 00000000000..05aa0f366bb
--- /dev/null
+++ b/app/assets/javascripts/ci/application.js.coffee
@@ -0,0 +1,40 @@
+# 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)
diff --git a/app/assets/javascripts/ci/build.coffee b/app/assets/javascripts/ci/build.coffee
new file mode 100644
index 00000000000..c30859b484b
--- /dev/null
+++ b/app/assets/javascripts/ci/build.coffee
@@ -0,0 +1,41 @@
+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
diff --git a/app/assets/javascripts/ci/projects.js.coffee b/app/assets/javascripts/ci/projects.js.coffee
new file mode 100644
index 00000000000..7e028b4e115
--- /dev/null
+++ b/app/assets/javascripts/ci/projects.js.coffee
@@ -0,0 +1,6 @@
+$(document).on 'click', '.badge-codes-toggle', ->
+ $('.badge-codes-block').toggleClass("hide")
+ return false
+
+$(document).on 'click', '.sync-now', ->
+ $(this).find('i').addClass('fa-spin')
diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee
index a0dcaa8c27a..6f789e668af 100644
--- a/app/assets/javascripts/dropzone_input.js.coffee
+++ b/app/assets/javascripts/dropzone_input.js.coffee
@@ -167,6 +167,7 @@ class @DropzoneInput
dataType: "json"
).success (data) ->
preview.html data.body
+ preview.syntaxHighlight()
renderReferencedUsers data.references.users
diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee
index 176d9cabefa..c4d3e619f5e 100644
--- a/app/assets/javascripts/issuable_context.js.coffee
+++ b/app/assets/javascripts/issuable_context.js.coffee
@@ -11,12 +11,13 @@ class @IssuableContext
$(this).submit()
$('.issuable-details').waitForImages ->
+ $('.issuable-affix').on 'affix.bs.affix', ->
+ $(@).width($(@).outerWidth())
+ .on 'affixed-top.bs.affix affixed-bottom.bs.affix', ->
+ $(@).width('')
+
$('.issuable-affix').affix offset:
top: ->
@top = ($('.issuable-affix').offset().top - 70)
bottom: ->
@bottom = $('.footer').outerHeight(true)
- $('.issuable-affix').on 'affix.bs.affix', ->
- $(@).width($(@).outerWidth())
- .on 'affixed-top.bs.affix affixed-bottom.bs.affix', ->
- $(@).width('')
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index 19a07b6a033..4e56791bde4 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -66,6 +66,11 @@ class @MergeRequestTabs
@setCurrentAction(action)
+ scrollToElement: (container) ->
+ if window.location.hash
+ top = $(container + " " + window.location.hash).offset().top
+ $('body').scrollTo(top);
+
# Activate a tab based on the current action
activateTab: (action) ->
action = 'notes' if action == 'show'
@@ -122,6 +127,7 @@ class @MergeRequestTabs
document.getElementById('commits').innerHTML = data.html
$('.js-timeago').timeago()
@commitsLoaded = true
+ @scrollToElement(".commits")
loadDiff: (source) ->
return if @diffsLoaded
@@ -131,6 +137,7 @@ class @MergeRequestTabs
success: (data) =>
document.getElementById('diffs').innerHTML = data.html
@diffsLoaded = true
+ @scrollToElement(".diffs")
toggleLoading: ->
$('.mr-loading-status .loading').toggle()
diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee
index 995a2f24093..3176e5a8965 100644
--- a/app/assets/javascripts/merge_request_widget.js.coffee
+++ b/app/assets/javascripts/merge_request_widget.js.coffee
@@ -15,11 +15,12 @@ class @MergeRequestWidget
type: 'GET'
url: $('.merge-request').data('url')
success: (data) =>
- switch data.state
- when 'merged'
- location.reload()
- else
- setTimeout(merge_request_widget.mergeInProgress, 2000)
+ if data.state == "merged"
+ location.reload()
+ else if data.merge_error
+ $('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>")
+ else
+ setTimeout(merge_request_widget.mergeInProgress, 2000)
dataType: 'json'
getMergeStatus: ->
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 0021d17d85e..4b9f0d68912 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -122,7 +122,9 @@ class @Notes
# or skip if rendered
if @isNewNote(note)
@note_ids.push(note.id)
- $('ul.main-notes-list').append(note.html)
+ $('ul.main-notes-list').
+ append(note.html).
+ syntaxHighlight()
@initTaskList()
###
@@ -275,13 +277,15 @@ class @Notes
Updates the current note field.
###
- updateNote: (xhr, note, status) =>
- note_li = $(".note-row-" + note.id)
- note_li.replaceWith(note.html)
- note_li.find('.note-edit-form').hide()
- note_li.find('.note-body > .note-text').show()
- note_li.find('js-task-list-container').taskList('enable')
- @enableTaskList()
+ updateNote: (_xhr, note, _status) =>
+ # Convert returned HTML to a jQuery object so we can modify it further
+ $html = $(note.html)
+ $html.syntaxHighlight()
+ $html.find('.js-task-list-container').taskList('enable')
+
+ # Find the note's `li` element by ID and replace it with the updated HTML
+ $note_li = $("#note_#{note.id}")
+ $note_li.replaceWith($html)
###
Called in response to clicking the edit note link
diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee
index 39a433dfc91..0ea8fffce07 100644
--- a/app/assets/javascripts/project.js.coffee
+++ b/app/assets/javascripts/project.js.coffee
@@ -24,3 +24,19 @@ class @Project
$.cookie('hide_no_password_message', 'false', { path: path })
$(@).parents('.no-password-message').remove()
e.preventDefault()
+
+ $('.update-notification').on 'click', (e) ->
+ e.preventDefault()
+ notification_level = $(@).data 'notification-level'
+ $('#notification_level').val(notification_level)
+ $('#notification-form').submit()
+ label = null
+ switch notification_level
+ when 0 then label = ' Disabled '
+ when 1 then label = ' Participating '
+ when 2 then label = ' Watching '
+ when 3 then label = ' Global '
+ when 4 then label = ' On Mention '
+ $('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>")
+ $(@).parents('ul').find('li.active').removeClass 'active'
+ $(@).parent().addClass 'active' \ No newline at end of file
diff --git a/app/assets/javascripts/syntax_highlight.coffee b/app/assets/javascripts/syntax_highlight.coffee
index 510f15d1b49..980f0232d10 100644
--- a/app/assets/javascripts/syntax_highlight.coffee
+++ b/app/assets/javascripts/syntax_highlight.coffee
@@ -1,3 +1,5 @@
+# Syntax Highlighter
+#
# Applies a syntax highlighting color scheme CSS class to any element with the
# `js-syntax-highlight` class
#
@@ -5,5 +7,14 @@
#
# <div class="js-syntax-highlight"></div>
#
+$.fn.syntaxHighlight = ->
+ if $(this).hasClass('js-syntax-highlight')
+ # Given the element itself, apply highlighting
+ $(this).addClass(gon.user_color_scheme)
+ else
+ # Given a parent element, recurse to any of its applicable children
+ $children = $(this).find('.js-syntax-highlight')
+ $children.syntaxHighlight() if $children.length
+
$(document).on 'ready page:load', ->
- $('.js-syntax-highlight').addClass(gon.user_color_scheme)
+ $('.js-syntax-highlight').syntaxHighlight()
diff --git a/app/assets/javascripts/tree.js.coffee b/app/assets/javascripts/tree.js.coffee
index d428db5b422..de8eebcd0b2 100644
--- a/app/assets/javascripts/tree.js.coffee
+++ b/app/assets/javascripts/tree.js.coffee
@@ -16,6 +16,9 @@ class @TreeView
li = $("tr.tree-item")
liSelected = null
$('body').keydown (e) ->
+ if $("input:focus").length > 0 && (e.which == 38 || e.which == 40)
+ return false
+
if e.which is 40
if liSelected
next = liSelected.next()
@@ -38,4 +41,4 @@ class @TreeView
$(liSelected).focus()
else if e.which is 13
path = $('.tree-item.selected .tree-item-file-name a').attr('href')
- Turbolinks.visit(path)
+ if path then Turbolinks.visit(path)
diff --git a/app/assets/javascripts/zen_mode.js.coffee b/app/assets/javascripts/zen_mode.js.coffee
index 8a0564a9098..a1462cf3cae 100644
--- a/app/assets/javascripts/zen_mode.js.coffee
+++ b/app/assets/javascripts/zen_mode.js.coffee
@@ -38,6 +38,8 @@ class @ZenMode
@active_checkbox = $(checkbox)
@active_checkbox.prop('checked', true)
@active_zen_area = @active_checkbox.parent().find('textarea')
+ # Prevent a user-resized textarea from persisting to fullscreen
+ @active_zen_area.removeAttr('style')
@active_zen_area.focus()
exitZenMode: =>
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 1a5f11df7d1..d9ede637944 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -12,6 +12,7 @@
*/
+@import "base/fonts";
@import "base/variables";
@import "base/mixins";
@import "base/layout";
@@ -60,3 +61,9 @@
* Styles for JS behaviors.
*/
@import "behaviors.scss";
+
+/**
+ * CI specific styles:
+ */
+@import "ci/**/*";
+
diff --git a/app/assets/stylesheets/base/fonts.scss b/app/assets/stylesheets/base/fonts.scss
new file mode 100644
index 00000000000..e214567eca1
--- /dev/null
+++ b/app/assets/stylesheets/base/fonts.scss
@@ -0,0 +1,25 @@
+/* latin-ext */
+@font-face {
+ font-family: 'Source Sans Pro';
+ font-style: normal;
+ font-weight: 300;
+ src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), font-url('SourceSansPro-Light.ttf');
+}
+@font-face {
+ font-family: 'Source Sans Pro';
+ font-style: normal;
+ font-weight: 400;
+ src: local('Source Sans Pro'), local('SourceSansPro-Regular'), font-url('SourceSansPro-Regular.ttf');
+}
+@font-face {
+ font-family: 'Source Sans Pro';
+ font-style: normal;
+ font-weight: 600;
+ src: local('Source Sans Pro Semibold'), local('SourceSansPro-Semibold'), font-url('SourceSansPro-Semibold.ttf');
+}
+@font-face {
+ font-family: 'Source Sans Pro';
+ font-style: normal;
+ font-weight: 700;
+ src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), font-url('SourceSansPro-Bold.ttf');
+}
diff --git a/app/assets/stylesheets/base/gl_bootstrap.scss b/app/assets/stylesheets/base/gl_bootstrap.scss
index 21acbfa5e5a..eb8d23d6453 100644
--- a/app/assets/stylesheets/base/gl_bootstrap.scss
+++ b/app/assets/stylesheets/base/gl_bootstrap.scss
@@ -85,14 +85,14 @@
// Labels
.label {
padding: 2px 4px;
- font-size: 12px;
+ font-size: 13px;
font-style: normal;
font-weight: normal;
display: inline-block;
&.label-gray {
- background-color: #eee;
- color: #999;
+ background-color: #f8fafc;
+ color: $gl-gray;
text-shadow: none;
}
@@ -156,10 +156,16 @@
* Add some extra stuff to panels
*
*/
+
+.container-blank .panel .panel-heading {
+ font-size: 17px;
+ line-height: 38px;
+}
+
.panel {
- .panel-heading {
- font-weight: bold;
+ box-shadow: none;
+ .panel-heading {
.panel-head-actions {
position: relative;
top: -5px;
@@ -182,6 +188,10 @@
.pagination {
margin: 0;
}
+
+ .btn {
+ min-width: 124px;
+ }
}
&.panel-small {
@@ -209,6 +219,12 @@
}
}
+.alert-help {
+ background-color: $background-color;
+ border: 1px solid $border-color;
+ color: $gl-gray;
+}
+
// Typography =================================================================
.text-primary,
diff --git a/app/assets/stylesheets/base/gl_variables.scss b/app/assets/stylesheets/base/gl_variables.scss
index 56f4c794e1b..7378d404008 100644
--- a/app/assets/stylesheets/base/gl_variables.scss
+++ b/app/assets/stylesheets/base/gl_variables.scss
@@ -22,6 +22,10 @@ $brand-info: $gl-info;
$brand-warning: $gl-warning;
$brand-danger: $gl-danger;
+$border-radius-base: 3px !default;
+$border-radius-large: 5px !default;
+$border-radius-small: 2px !default;
+
//== Scaffolding
//
@@ -42,17 +46,18 @@ $font-size-base: $gl-font-size;
//
//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
-$padding-base-vertical: 6px;
-$padding-base-horizontal: 14px;
-
+$padding-base-vertical: 9px;
+$padding-base-horizontal: $gl-padding;
+$component-active-color: #fff;
+$component-active-bg: $brand-info;
//== Forms
//
//##
$input-color: $text-color;
-$input-border: #DDD;
-$input-border-focus: $brand-info;
+$input-border: #e7e9ed;
+$input-border-focus: #7F8FA4;
$legend-color: $text-color;
@@ -60,20 +65,20 @@ $legend-color: $text-color;
//
//##
-$pagination-color: #fff;
-$pagination-bg: $brand-success;
+$pagination-color: $gl-gray;
+$pagination-bg: $background-color;
$pagination-border: transparent;
$pagination-hover-color: #fff;
-$pagination-hover-bg: darken($brand-success, 15%);
+$pagination-hover-bg: $brand-info;
$pagination-hover-border: transparent;
$pagination-active-color: #fff;
-$pagination-active-bg: darken($brand-success, 15%);
+$pagination-active-bg: $brand-info;
$pagination-active-border: transparent;
-$pagination-disabled-color: #b4bcc2;
-$pagination-disabled-bg: lighten($brand-success, 15%);
+$pagination-disabled-color: #fff;
+$pagination-disabled-bg: lighten($brand-info, 15%);
$pagination-disabled-border: transparent;
@@ -109,11 +114,12 @@ $alert-border-radius: 0;
//
//##
-$panel-border-radius: 0;
-$panel-default-text: $text-color;
-$panel-default-border: $border-color;
-$panel-default-heading-bg: $background-color;
-
+$panel-border-radius: 2px;
+$panel-default-text: $text-color;
+$panel-default-border: $border-color;
+$panel-default-heading-bg: $background-color;
+$panel-footer-bg: $background-color;
+$panel-inner-border: $border-color;
//== Wells
//
@@ -131,3 +137,22 @@ $code-bg: #f9f2f4;
$kbd-color: #fff;
$kbd-bg: #333;
+
+//== Buttons
+//
+//##
+$btn-default-color: $gl-text-color;
+$btn-default-bg: #fff;
+$btn-default-border: #e7e9ed;
+
+//== Nav
+//
+//##
+$nav-link-padding: 13px $gl-padding;
+
+//== Code
+//
+//##
+$pre-bg: #f8fafc !default;
+$pre-color: $gl-gray !default;
+$pre-border-color: #e7e9ed;
diff --git a/app/assets/stylesheets/base/layout.scss b/app/assets/stylesheets/base/layout.scss
index 734b95e26c0..b91c15d8910 100644
--- a/app/assets/stylesheets/base/layout.scss
+++ b/app/assets/stylesheets/base/layout.scss
@@ -21,7 +21,6 @@ html {
margin-top: 30px;
}
-
.container-limited {
max-width: $fixed-layout-width;
}
diff --git a/app/assets/stylesheets/base/mixins.scss b/app/assets/stylesheets/base/mixins.scss
index 05f5bd79f91..c74a6d39824 100644
--- a/app/assets/stylesheets/base/mixins.scss
+++ b/app/assets/stylesheets/base/mixins.scss
@@ -55,8 +55,11 @@
}
@mixin md-typography {
- font-size: 15px;
- line-height: 1.5;
+ color: $md-text-color;
+
+ a {
+ color: $md-link-color;
+ }
img {
max-width: 100%;
@@ -90,46 +93,88 @@
}
h1 {
- margin-top: 45px;
- font-size: 2.5em;
+ font-size: 1.3em;
+ font-weight: 600;
+ margin: 24px 0 12px 0;
+ padding: 0 0 10px 0;
+ border-bottom: 1px solid #e7e9ed;
+ color: #313236;
}
h2 {
- margin-top: 40px;
- font-size: 2em;
+ font-size: 1.2em;
+ font-weight: 600;
+ margin: 24px 0 12px 0;
+ color: #313236;
}
h3 {
- margin-top: 35px;
- font-size: 1.5em;
+ margin: 24px 0 12px 0;
+ font-size: 1.25em;
}
h4 {
- margin-top: 30px;
- font-size: 1.2em;
+ margin: 24px 0 12px 0;
+ font-size: 1.1em;
+ }
+
+ h5 {
+ margin: 24px 0 12px 0;
+ font-size: 1em;
+ }
+
+ h6 {
+ margin: 24px 0 12px 0;
+ font-size: 0.90em;
}
blockquote {
- color: #888;
+ padding: 8px 21px;
+ margin: 12px 0 12px;
+ border-left: 3px solid #e7e9ed;
+ }
+
+ blockquote p {
+ color: #7f8fa4 !important;
font-size: 15px;
line-height: 1.5;
}
+ p {
+ color:#5c5d5e;
+ margin:6px 0 0 0;
+ }
+
table {
@extend .table;
@extend .table-bordered;
+ margin: 12px 0 12px 0;
+ color: #5c5d5e;
th {
- background: #EEE;
+ background: #f8fafc;
}
}
+ pre {
+ margin: 12px 0 12px 0 !important;
+ background-color: #f8fafc !important;
+ font-size: 13px !important;
+ color: #5b6169 !important;
+ line-height: 1.6em !important;
+ @include border-radius(2px);
+ }
+
p > code {
- font-size: inherit;
font-weight: inherit;
}
+
+ ul {
+ color: #5c5d5e;
+ }
+
li {
- line-height: 1.5;
+ line-height: 1.6em;
}
a[href*="/uploads/"], a[href*="storage.googleapis.com/google-code-attachments/"] {
@@ -149,6 +194,7 @@
}
}
+
@mixin str-truncated($max_width: 82%) {
display: inline-block;
overflow: hidden;
@@ -166,7 +212,7 @@
padding: 0px;
list-style: none;
- li {
+ > li {
padding: 10px 0;
border-bottom: 1px solid #EEE;
overflow: hidden;
@@ -174,13 +220,13 @@
margin: 0px;
&:last-child {
- border:none
+ border-bottom: none;
}
&.active {
background: #f9f9f9;
a {
- font-weight: bold;
+ font-weight: 600;
}
}
@@ -190,8 +236,66 @@
&.light {
a {
- color: #777;
+ color: $gl-gray;
}
}
}
}
+
+@mixin input-big {
+ height: 36px;
+ padding: 5px 10px;
+ font-size: 16px;
+ line-height: 24px;
+ color: #7f8fa4;
+ background-color: #fff;
+ border-color: #e7e9ed;
+}
+
+@mixin btn-big {
+ height: 36px;
+ padding: 5px 10px;
+ font-size: 16px;
+ line-height: 24px;
+}
+
+@mixin nav-menu {
+ padding: 0;
+ margin: 0;
+ list-style: none;
+ margin-top: 5px;
+ height: 56px;
+
+ li {
+ display: inline-block;
+
+ a {
+ padding: 14px;
+ font-size: 17px;
+ line-height: 28px;
+ color: #7f8fa4;
+ border-bottom: 2px solid transparent;
+
+ &:hover, &:active, &:focus {
+ text-decoration: none;
+ }
+ }
+
+ &.active a {
+ color: #4c4e54;
+ border-bottom: 2px solid #1cacfc;
+ }
+
+ .badge {
+ font-weight: normal;
+ background-color: #fff;
+ background-color: #eee;
+ color: #78a;
+ }
+ }
+}
+
+.fa-align {
+ top: 20px;
+ position: relative;
+}
diff --git a/app/assets/stylesheets/base/variables.scss b/app/assets/stylesheets/base/variables.scss
index 26d0a1e5363..befd63832d5 100644
--- a/app/assets/stylesheets/base/variables.scss
+++ b/app/assets/stylesheets/base/variables.scss
@@ -1,27 +1,35 @@
-$style_color: #474D57;
$hover: #FFFAF1;
-$gl-text-color: #222222;
-$gl-link-color: #446e9b;
+$gl-text-color: #54565B;
+$gl-text-green: #4A2;
+$gl-text-red: #D12F19;
+$gl-text-orange: #D90;
+$gl-header-color: #4c4e54;
+$gl-link-color: #333c48;
+$md-text-color: #444;
+$md-link-color: #3084bb;
$nprogress-color: #c0392b;
-$gl-font-size: 14px;
+$gl-font-size: 15px;
$list-font-size: 15px;
-$sidebar_collapsed_width: 52px;
+$sidebar_collapsed_width: 62px;
$sidebar_width: 230px;
$avatar_radius: 50%;
$code_font_size: 13px;
$code_line_height: 1.5;
-$border-color: #E5E5E5;
-$background-color: #f5f5f5;
-$header-height: 50px;
+$border-color: #dce0e6;
+$background-color: #F7F8FA;
+$header-height: 58px;
$fixed-layout-width: 1200px;
+$gl-gray: #7f8fa4;
+$gl-padding: 16px;
+$gl-avatar-size: 46px;
/*
* State colors:
*/
$gl-primary: #446e9b;
-$gl-success: #019875;
-$gl-info: #029ACF;
+$gl-success: #44c679;
+$gl-info: #00aaff;
$gl-warning: #EB9532;
$gl-danger: #d9534f;
@@ -35,4 +43,4 @@ $deleted: #f77;
* Fonts
*/
$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
-$regular_font: "Helvetica Neue", Helvetica, Arial, sans-serif;
+$regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif;
diff --git a/app/assets/stylesheets/ci/builds.scss b/app/assets/stylesheets/ci/builds.scss
new file mode 100644
index 00000000000..a27dd0db581
--- /dev/null
+++ b/app/assets/stylesheets/ci/builds.scss
@@ -0,0 +1,76 @@
+.build-page {
+ 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;
+ }
+ }
+
+ .build-top-menu {
+ margin-top: 0;
+ margin-bottom: 2px;
+ }
+}
+
diff --git a/app/assets/stylesheets/ci/lint.scss b/app/assets/stylesheets/ci/lint.scss
new file mode 100644
index 00000000000..6d2bd33b28b
--- /dev/null
+++ b/app/assets/stylesheets/ci/lint.scss
@@ -0,0 +1,10 @@
+.ci-body {
+ .incorrect-syntax{
+ font-size: 19px;
+ color: red;
+ }
+ .correct-syntax{
+ font-size: 19px;
+ color: #47a447;
+ }
+}
diff --git a/app/assets/stylesheets/ci/projects.scss b/app/assets/stylesheets/ci/projects.scss
new file mode 100644
index 00000000000..8c5273abcda
--- /dev/null
+++ b/app/assets/stylesheets/ci/projects.scss
@@ -0,0 +1,59 @@
+.ci-body {
+ .project-title {
+ margin: 0;
+ color: #444;
+ font-size: 20px;
+ line-height: 1.5;
+ }
+
+ .wide-table-holder {
+ margin-left: -$gl-padding;
+ margin-right: -$gl-padding;
+ }
+
+ .builds,
+ .projects-table {
+ .light {
+ border-color: $border-color;
+ }
+
+ th, td {
+ padding: 10px $gl-padding;
+ }
+
+ td {
+ color: $gl-gray;
+ vertical-align: middle !important;
+
+ a {
+ font-weight: normal;
+ text-decoration: none;
+ }
+ }
+ }
+
+ .commit-info {
+ .attr-name {
+ margin-right: 5px;
+ }
+
+ pre.commit-message {
+ background: none;
+ padding: 0;
+ margin: 0;
+ border: none;
+ margin: 20px 0;
+ border-radius: 0;
+ }
+ }
+
+ .loading{
+ font-size: 20px;
+ }
+
+ .ci-charts {
+ fieldset {
+ margin-bottom: 16px;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/ci/runners.scss b/app/assets/stylesheets/ci/runners.scss
new file mode 100644
index 00000000000..2b15ab83129
--- /dev/null
+++ b/app/assets/stylesheets/ci/runners.scss
@@ -0,0 +1,36 @@
+.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;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/ci/status.scss b/app/assets/stylesheets/ci/status.scss
new file mode 100644
index 00000000000..a7d3b2197f1
--- /dev/null
+++ b/app/assets/stylesheets/ci/status.scss
@@ -0,0 +1,37 @@
+.ci-status {
+ padding: 2px 7px;
+ margin-right: 5px;
+ border: 1px solid #EEE;
+ white-space: nowrap;
+ @include border-radius(4px);
+
+ &:hover {
+ text-decoration: none;
+ }
+
+ &.ci-failed {
+ color: $gl-danger;
+ border-color: $gl-danger;
+ }
+
+ &.ci-success {
+ color: $gl-success;
+ border-color: $gl-success;
+ }
+
+ &.ci-info {
+ color: $gl-info;
+ border-color: $gl-info;
+ }
+
+ &.ci-disabled {
+ color: $gl-gray;
+ border-color: $gl-gray;
+ }
+
+ &.ci-pending,
+ &.ci-running {
+ color: $gl-warning;
+ border-color: $gl-warning;
+ }
+}
diff --git a/app/assets/stylesheets/ci/xterm.scss b/app/assets/stylesheets/ci/xterm.scss
new file mode 100644
index 00000000000..532dede0b23
--- /dev/null
+++ b/app/assets/stylesheets/ci/xterm.scss
@@ -0,0 +1,906 @@
+.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;
+ }
+}
diff --git a/app/assets/stylesheets/generic/avatar.scss b/app/assets/stylesheets/generic/avatar.scss
index 8595887c3b9..36e582d4854 100644
--- a/app/assets/stylesheets/generic/avatar.scss
+++ b/app/assets/stylesheets/generic/avatar.scss
@@ -23,8 +23,13 @@
&.s24 { width: 24px; height: 24px; margin-right: 8px; }
&.s26 { width: 26px; height: 26px; margin-right: 8px; }
&.s32 { width: 32px; height: 32px; margin-right: 10px; }
+ &.s36 { width: 36px; height: 36px; margin-right: 10px; }
+ &.s46 { width: 46px; height: 46px; margin-right: 15px; }
+ &.s48 { width: 48px; height: 48px; margin-right: 10px; }
&.s60 { width: 60px; height: 60px; margin-right: 12px; }
&.s90 { width: 90px; height: 90px; margin-right: 15px; }
+ &.s110 { width: 110px; height: 110px; margin-right: 15px; }
+ &.s140 { width: 140px; height: 140px; margin-right: 20px; }
&.s160 { width: 160px; height: 160px; margin-right: 20px; }
}
@@ -38,5 +43,7 @@
&.s32 { font-size: 22px; line-height: 32px; }
&.s60 { font-size: 32px; line-height: 60px; }
&.s90 { font-size: 36px; line-height: 90px; }
- &.s160 { font-size: 96px; line-height: 1.33; }
+ &.s110 { font-size: 40px; line-height: 112px; font-weight: 300; }
+ &.s140 { font-size: 72px; line-height: 140px; }
+ &.s160 { font-size: 96px; line-height: 160px; }
}
diff --git a/app/assets/stylesheets/generic/blocks.scss b/app/assets/stylesheets/generic/blocks.scss
index 3536a68f416..6ce34b5c3e8 100644
--- a/app/assets/stylesheets/generic/blocks.scss
+++ b/app/assets/stylesheets/generic/blocks.scss
@@ -1,19 +1,62 @@
.light-well {
- background: #f9f9f9;
+ background-color: #f8fafc;
padding: 15px;
}
.centered-light-block {
text-align: center;
- color: #888;
+ color: $gl-gray;
margin: 20px;
}
.nothing-here-block {
text-align: center;
padding: 20px;
- color: #666;
+ color: $gl-gray;
font-weight: normal;
font-size: 16px;
line-height: 36px;
}
+
+.gray-content-block {
+ margin: -$gl-padding;
+ background-color: $background-color;
+ padding: $gl-padding;
+ margin-bottom: 0px;
+ border-top: 1px solid $border-color;
+ border-bottom: 1px solid $border-color;
+ color: $gl-gray;
+
+ &.top-block {
+ border-top: none;
+ }
+
+ &.middle-block {
+ margin-top: 0;
+ margin-bottom: 0;
+ }
+
+ &.clear-block {
+ margin-bottom: $gl-padding - 1px;
+ padding-bottom: $gl-padding;
+ }
+
+ &.second-block {
+ margin-top: -1px;
+ margin-bottom: 0;
+ }
+
+ &.footer-block {
+ margin-top: 0;
+ border-bottom: none;
+ margin-bottom: -$gl-padding;
+ }
+
+ .title {
+ color: $gl-text-color;
+ }
+
+ .oneline {
+ line-height: 42px;
+ }
+}
diff --git a/app/assets/stylesheets/generic/buttons.scss b/app/assets/stylesheets/generic/buttons.scss
index cd6bf64c0ae..cf76f538e01 100644
--- a/app/assets/stylesheets/generic/buttons.scss
+++ b/app/assets/stylesheets/generic/buttons.scss
@@ -1,3 +1,6 @@
+body {
+ text-rendering: geometricPrecision;
+}
.btn {
@extend .btn-default;
@@ -10,7 +13,7 @@
}
&.btn-save {
- @extend .btn-primary;
+ @extend .btn-success;
}
&.btn-remove {
@@ -72,3 +75,154 @@
}
}
}
+
+.btn-group-next {
+ .btn {
+ padding: 9px 0px;
+ font-size: 15px;
+ color: #7f8fa4;
+ border-color: #e7e9ed;
+ width: 140px;
+
+ &.active {
+ border-color: $gl-info;
+ background: $gl-info;
+ color: #fff;
+ }
+ }
+}
+
+@mixin btn-info {
+ @include border-radius(2px);
+
+ border-width: 1px;
+ border-style: solid;
+ text-transform: uppercase;
+ font-size: 13px;
+ font-weight: 600;
+ line-height: 18px;
+ padding: 11px 16px;
+ letter-spacing: .4px;
+
+ &:hover {
+ border-width: 1px;
+ border-style: solid;
+ }
+
+ &:focus {
+ border-width: 1px;
+ border-style: solid;
+ }
+
+ &:active {
+ @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
+ border-width: 1px;
+ border-style: solid;
+ }
+}
+
+@mixin btn-middle {
+ @include border-radius(2px);
+
+ border-width: 1px;
+ border-style: solid;
+ text-transform: uppercase;
+ font-size: 13px;
+ font-weight: 600;
+ line-height: 18px;
+ padding: 11px 24px;
+ letter-spacing: .4px;
+
+ &:hover {
+ border-width: 1px;
+ border-style: solid;
+ }
+
+ &:focus {
+ border-width: 1px;
+ border-style: solid;
+ }
+
+ &:active {
+ @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
+ border-width: 1px;
+ border-style: solid;
+ }
+}
+
+
+@mixin btn-green {
+ background-color: #28b061;
+ border: 1px solid #26a65c;
+ color: #fff;
+
+ &:hover {
+ background-color: #26ab5d;
+ border: 1px solid #229954;
+ color: #fff;
+ }
+
+ &:focus {
+ background-color: #26ab5d;
+ border: 1px solid #229954;
+ color: #fff;
+ }
+
+ &:active {
+ @include box-shadow (inset 0 0 4px rgba(0, 0, 0, 0.12));
+
+ background-color: #23a158 !important;
+ border: 1px solid #229954 !important;
+ color: #fff !important;
+ }
+}
+
+/*Butons*/
+
+@mixin bnt-project {
+ background-color: #f0f2f5;
+ border-color: #dce0e5;
+ color: #313236;
+
+ &:hover {
+ border-color:#dce0e5;
+ background-color: #ebeef2;
+ color: #313236;
+ }
+
+ &:focus {
+ border-color: #dce0e5;
+ background-color: #ebeef2;
+ color: #313236;
+ }
+
+ &:active {
+ @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
+
+ color: #313236 !important;
+ border-color: #c6cacf !important;
+ background-color: #e4e7ed !important;
+ }
+}
+
+@mixin btn-remove {
+ background-color: #f72e60;
+ border-color: #ee295a;
+
+ &:hover {
+ background-color: #e82757;
+ border-color: #e32555;
+ }
+
+ &:focus {
+ background-color: #e82757;
+ border-color: #e32555;
+ }
+
+ &:active {
+ @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
+ background-color: #d42450 !important;
+ border-color: #e12554 !important;
+ }
+
+} \ No newline at end of file
diff --git a/app/assets/stylesheets/generic/callout.scss b/app/assets/stylesheets/generic/callout.scss
new file mode 100644
index 00000000000..f1699d21c9b
--- /dev/null
+++ b/app/assets/stylesheets/generic/callout.scss
@@ -0,0 +1,45 @@
+/*
+ * Callouts from Bootstrap3 docs
+ *
+ * Not quite alerts, but custom and helpful notes for folks reading the docs.
+ * Requires a base and modifier class.
+ */
+
+/* Common styles for all types */
+.bs-callout {
+ margin: 20px 0;
+ padding: 20px;
+ border-left: 3px solid #eee;
+ color: #666;
+ background: #f9f9f9;
+}
+.bs-callout h4 {
+ margin-top: 0;
+ margin-bottom: 5px;
+}
+.bs-callout p:last-child {
+ margin-bottom: 0;
+}
+
+/* Variations */
+.bs-callout-danger {
+ background-color: #fdf7f7;
+ border-color: #eed3d7;
+ color: #b94a48;
+}
+.bs-callout-warning {
+ background-color: #faf8f0;
+ border-color: #faebcc;
+ color: #8a6d3b;
+}
+.bs-callout-info {
+ background-color: #f4f8fa;
+ border-color: #bce8f1;
+ color: #34789a;
+}
+.bs-callout-success {
+ background-color: #dff0d8;
+ border-color: #5cA64d;
+ color: #3c763d;
+}
+
diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss
index e5902597c4d..45e284542d2 100644
--- a/app/assets/stylesheets/generic/common.scss
+++ b/app/assets/stylesheets/generic/common.scss
@@ -1,12 +1,13 @@
/** COLORS **/
-.cgray { color: gray }
+.cgray { color: $gl-gray; }
.clgray { color: #BBB }
-.cred { color: #D12F19 }
-.cgreen { color: #4a2 }
+.cred { color: $gl-text-red; }
+.cgreen { color: $gl-text-green; }
.cdark { color: #444 }
/** COMMON CLASSES **/
.prepend-top-10 { margin-top:10px }
+.prepend-top-default { margin-top: $gl-padding; }
.prepend-top-20 { margin-top:20px }
.prepend-left-10 { margin-left:10px }
.prepend-left-20 { margin-left:20px }
@@ -20,10 +21,10 @@
.underlined-link { text-decoration: underline; }
.hint { font-style: italic; color: #999; }
-.light { color: #888 }
+.light { color: $gl-gray; }
.slead {
- color: #666;
+ color: $gl-gray;
font-size: 15px;
margin-bottom: 12px;
font-weight: normal;
@@ -74,8 +75,6 @@ pre {
color: $gl-link-color;
}
-.help li { color:$style_color; }
-
.back-link {
font-size: 14px;
}
@@ -303,7 +302,7 @@ table {
}
.btn-sign-in {
- margin-top: 7px;
+ margin-top: 8px;
text-shadow: none;
}
@@ -314,7 +313,7 @@ table {
}
.wiki .highlight, .note-body .highlight {
- margin-bottom: 9px;
+ margin: 12px 0 12px 0;
}
.wiki .code {
@@ -355,14 +354,14 @@ table {
}
.description {
- font-size: 16px;
+ font-size: $gl-font-size;
color: #666;
margin-top: 8px;
}
}
.profiler-results {
- top: 50px !important;
+ top: 73px !important;
.profiler-button,
.profiler-controls {
@@ -371,21 +370,27 @@ table {
}
.center-top-menu {
- list-style: none;
+ @include nav-menu;
text-align: center;
margin-top: 5px;
- padding-bottom: 15px;
- margin-bottom: 15px;
-
- li {
- display: inline-block;
+ margin-bottom: $gl-padding;
+ height: 56px;
+ margin-top: -$gl-padding;
+ padding-top: $gl-padding;
- a {
- padding: 15px;
- }
+ &.no-bottom {
+ margin-bottom: 0;
+ }
- &.active a {
- color: #666;
- }
+ &.no-top {
+ margin-top: 0;
}
}
+
+.dropzone .dz-preview .dz-progress {
+ border-color: $border-color !important;
+}
+
+.dropzone .dz-preview .dz-progress .dz-upload {
+ background: $gl-success !important;
+}
diff --git a/app/assets/stylesheets/generic/files.scss b/app/assets/stylesheets/generic/files.scss
index f845342c67b..9dd77747884 100644
--- a/app/assets/stylesheets/generic/files.scss
+++ b/app/assets/stylesheets/generic/files.scss
@@ -3,7 +3,11 @@
*
*/
.file-holder {
- border: 1px solid $border-color;
+ margin-left: -$gl-padding;
+ margin-right: -$gl-padding;
+ border: none;
+ border-top: 1px solid #E7E9EE;
+ border-bottom: 1px solid #E7E9EE;
margin-bottom: 1em;
table {
@@ -49,7 +53,7 @@
}
&.wiki {
- padding: 25px;
+ padding: $gl-padding;
.highlight {
margin-bottom: 9px;
@@ -90,7 +94,7 @@
border-right: none;
}
background: #fff;
- padding: 8px;
+ padding: 10px $gl-padding;
}
.lines {
pre {
@@ -100,6 +104,33 @@
border: none;
}
}
+ img.avatar {
+ border: 0 none;
+ float: none;
+ margin: 0;
+ padding: 0;
+ }
+ td.blame-commit {
+ background: #f9f9f9;
+ min-width: 350px;
+
+ .commit-author-link {
+ color: #888;
+ }
+ }
+ td.blame-numbers {
+ pre {
+ color: #AAA;
+ white-space: pre;
+ }
+ background: #f1f1f1;
+ border-left: 1px solid #DDD;
+ }
+ td.lines {
+ code {
+ font-family: $monospace_font;
+ }
+ }
}
&.logs {
diff --git a/app/assets/stylesheets/generic/filters.scss b/app/assets/stylesheets/generic/filters.scss
index bd93a79722d..8e6922c9231 100644
--- a/app/assets/stylesheets/generic/filters.scss
+++ b/app/assets/stylesheets/generic/filters.scss
@@ -2,31 +2,6 @@
margin-right: 15px;
}
-.issues-state-filters {
- li.active a {
- border-color: #DDD !important;
-
- &, &:hover, &:active, &.active {
- background: #f5f5f5 !important;
- border-bottom: 1px solid #f5f5f5 !important;
- }
- }
-}
-
-.issues-details-filters {
- font-size: 13px;
- background: #f5f5f5;
- margin: -10px 0;
- padding: 10px 15px;
- margin-top: -15px;
- border-left: 1px solid #DDD;
- border-right: 1px solid #DDD;
-
- .btn {
- font-size: 13px;
- }
-}
-
@media (min-width: 800px) {
.issues-filters,
.issues_bulk_update {
diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss
index 6a29b32e196..543ce41ab52 100644
--- a/app/assets/stylesheets/generic/header.scss
+++ b/app/assets/stylesheets/generic/header.scss
@@ -24,29 +24,27 @@ header {
z-index: 100;
margin-bottom: 0;
min-height: $header-height;
+ background-color: #fff;
border: none;
- border-bottom: 1px solid #EEE;
.container-fluid {
- background: #FFF;
width: 100% !important;
filter: none;
+ padding: 0;
.nav > li > a {
- color: #888;
- font-size: 14px;
+ color: #7f8fa4;
+ font-size: 18px;
padding: 0;
- background-color: #f5f5f5;
margin: ($header-height - 28) / 2 0;
margin-left: 10px;
- border-radius: 40px;
height: 28px;
width: 28px;
line-height: 28px;
text-align: center;
&:hover, &:focus, &:active {
- background-color: #EEE;
+ background-color: #FFF;
}
}
@@ -56,6 +54,7 @@ header {
border-radius: 0;
position: absolute;
right: 2px;
+ top: 15px;
&:hover {
background-color: #EEE;
@@ -70,16 +69,16 @@ header {
.title {
margin: 0;
overflow: hidden;
- font-size: 18px;
+ font-size: 19px;
line-height: $header-height;
- font-weight: bold;
- color: #444;
+ font-weight: normal;
+ color: #4c4e54;
text-overflow: ellipsis;
vertical-align: top;
white-space: nowrap;
a {
- color: #444;
+ color: #4c4e54;
&:hover {
text-decoration: underline;
}
@@ -94,7 +93,7 @@ header {
.search {
margin-right: 10px;
margin-left: 10px;
- margin-top: ($header-height - 28) / 2;
+ margin-top: ($header-height - 36) / 2;
form {
margin: 0;
@@ -105,13 +104,8 @@ header {
width: 220px;
background-image: image-url("icon-search.png");
background-repeat: no-repeat;
- background-position: 10px;
- height: inherit;
- padding: 4px 6px;
- padding-left: 25px;
- font-size: 13px;
- background-color: #f5f5f5;
- border-color: #f5f5f5;
+ background-position: 195px;
+ @include input-big;
&:focus {
@include box-shadow(none);
diff --git a/app/assets/stylesheets/generic/issue_box.scss b/app/assets/stylesheets/generic/issue_box.scss
index 869e586839b..b1fb87a6830 100644
--- a/app/assets/stylesheets/generic/issue_box.scss
+++ b/app/assets/stylesheets/generic/issue_box.scss
@@ -5,10 +5,13 @@
*/
.issue-box {
+ @include border-radius(3px);
+
display: inline-block;
- padding: 4px 13px;
+ padding: 10px $gl-padding;
font-weight: normal;
- margin-right: 5px;
+ margin-right: 10px;
+ font-size: $gl-font-size;
&.issue-box-closed {
background-color: $gl-danger;
@@ -21,7 +24,7 @@
}
&.issue-box-open {
- background-color: $gl-success;
+ background-color: #019875;
color: #FFF;
}
diff --git a/app/assets/stylesheets/generic/lists.scss b/app/assets/stylesheets/generic/lists.scss
index 4b7ff84de2b..3bfed8de772 100644
--- a/app/assets/stylesheets/generic/lists.scss
+++ b/app/assets/stylesheets/generic/lists.scss
@@ -49,8 +49,6 @@
}
}
- .author { color: #999; }
-
.list-item-name {
float: left;
position: relative;
@@ -71,15 +69,6 @@
font-size: $list-font-size;
line-height: 18px;
}
-
- .row_title {
- color: $gray-dark;
-
- &:hover {
- color: $text-color;
- text-decoration: underline;
- }
- }
}
}
@@ -109,3 +98,28 @@ ul.bordered-list {
li.task-list-item {
list-style-type: none;
}
+
+ul.content-list {
+ @include basic-list;
+
+ margin: 0;
+ padding: 0;
+
+ > li {
+ padding: $gl-padding;
+ border-color: #f1f2f4;
+ margin-left: -$gl-padding;
+ margin-right: -$gl-padding;
+ color: $gl-gray;
+
+ .avatar {
+ margin-right: 15px;
+ }
+
+ .controls {
+ padding-top: 10px;
+ float: right;
+ }
+ }
+}
+
diff --git a/app/assets/stylesheets/generic/markdown_area.scss b/app/assets/stylesheets/generic/markdown_area.scss
index a4fc82e90bf..ed0333d2336 100644
--- a/app/assets/stylesheets/generic/markdown_area.scss
+++ b/app/assets/stylesheets/generic/markdown_area.scss
@@ -65,8 +65,11 @@
position: relative;
}
-.md-header ul {
- float: left;
+.md-header {
+ ul {
+ float: left;
+ margin-bottom: 1px;
+ }
}
.referenced-users {
@@ -80,7 +83,7 @@
.md-preview-holder {
background: #FFF;
border: 1px solid #ddd;
- min-height: 100px;
+ min-height: 169px;
padding: 5px;
box-shadow: none;
}
@@ -105,7 +108,7 @@
.markdown-area {
background: #FFF;
border: 1px solid #ddd;
- min-height: 100px;
+ min-height: 140px;
padding: 5px;
box-shadow: none;
width: 100%;
diff --git a/app/assets/stylesheets/generic/mobile.scss b/app/assets/stylesheets/generic/mobile.scss
index bb7b9356c70..36ae126f865 100644
--- a/app/assets/stylesheets/generic/mobile.scss
+++ b/app/assets/stylesheets/generic/mobile.scss
@@ -80,6 +80,23 @@
%ul.notes .note-role, .note-actions {
display: none;
}
+
+ .center-top-menu {
+ height: 45px;
+
+ li a {
+ font-size: 14px;
+ padding: 19px 10px;
+ }
+ }
+
+ .projects-search-form {
+ margin: 0 -5px !important;
+
+ .btn {
+ display: none;
+ }
+ }
}
@media (max-width: $screen-sm-max) {
diff --git a/app/assets/stylesheets/generic/pagination.scss b/app/assets/stylesheets/generic/pagination.scss
new file mode 100644
index 00000000000..6677f94dafd
--- /dev/null
+++ b/app/assets/stylesheets/generic/pagination.scss
@@ -0,0 +1,34 @@
+.gl-pagination {
+ border-top: 1px solid $border-color;
+ background-color: $background-color;
+ margin: -$gl-padding;
+ margin-top: 0;
+
+ .pagination {
+ padding: 0;
+ margin: 0;
+ display: block;
+
+ li.first,
+ li.last,
+ li.next,
+ li.prev {
+ > a {
+ color: $link-color;
+
+ &:hover {
+ color: #fff;
+ }
+ }
+ }
+
+ li > a,
+ li > span {
+ border: none;
+ margin: 0;
+ @include border-radius(0 !important);
+ padding: 13px 19px;
+ border-right: 1px solid $border-color;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/generic/selects.scss b/app/assets/stylesheets/generic/selects.scss
index d8e0dc028d1..f0860de1c49 100644
--- a/app/assets/stylesheets/generic/selects.scss
+++ b/app/assets/stylesheets/generic/selects.scss
@@ -3,9 +3,9 @@
.select2-choice {
background: #FFF;
border-color: #DDD;
- height: 34px;
- padding: 6px 14px;
- font-size: 14px;
+ height: 42px;
+ padding: 8px $gl-padding;
+ font-size: $gl-font-size;
line-height: 1.42857143;
@include border-radius(4px);
@@ -13,7 +13,7 @@
.select2-arrow {
background: #FFF;
border-left: none;
- padding-top: 3px;
+ padding-top: 5px;
}
}
}
diff --git a/app/assets/stylesheets/generic/sidebar.scss b/app/assets/stylesheets/generic/sidebar.scss
index 320bdb1c765..c5ea3aca7ca 100644
--- a/app/assets/stylesheets/generic/sidebar.scss
+++ b/app/assets/stylesheets/generic/sidebar.scss
@@ -18,14 +18,27 @@
}
.content-wrapper {
+ min-height: 100vh;
width: 100%;
padding: 20px;
- background: #FFF;
+ background: #EAEBEC;
+
+ .container-fluid {
+ background: #FFF;
+ padding: $gl-padding;
+ min-height: 90vh;
+
+ &.container-blank {
+ background: none;
+ padding: 0;
+ border: none;
+ }
+ }
}
.nav-sidebar {
- margin-top: 29 + $header-height;
- margin-bottom: 50px;
+ margin-top: 14 + $header-height;
+ margin-bottom: 100px;
transition-duration: .3s;
list-style: none;
overflow: hidden;
@@ -43,13 +56,14 @@
}
a {
- padding: 8px 15px;
- font-size: 13px;
- line-height: 18px;
+ padding: 7px 15px;
+ font-size: $gl-font-size;
+ line-height: 24px;
color: $gray;
display: block;
text-decoration: none;
- padding-left: 16px;
+ padding-left: 22px;
+ font-weight: normal;
&:hover {
text-decoration: none;
@@ -60,9 +74,9 @@
}
i {
- width: 20px;
+ width: 16px;
color: $gray-light;
- margin-right: 23px;
+ margin-right: 13px;
}
.count {
@@ -108,41 +122,59 @@
}
@mixin folded-sidebar {
- padding-left: 50px;
+ padding-left: 60px;
transition-duration: .3s;
.sidebar-wrapper {
width: $sidebar_collapsed_width;
+ .header-logo {
+ width: $sidebar_collapsed_width;
+
+ a {
+ padding-left: 12px;
+
+ .gitlab-text-container {
+ display: none;
+ }
+ }
+ }
+
.nav-sidebar {
width: $sidebar_collapsed_width;
li a {
- padding-left: 16px;
+ span {
+ display: none;
+ }
}
}
.collapse-nav a {
- left: 0px;
width: $sidebar_collapsed_width;
}
.sidebar-user {
+ padding-left: 12px;
width: $sidebar_collapsed_width;
+
+ .username {
+ display: none;
+ }
}
}
}
.collapse-nav a {
+ width: $sidebar_width;
position: fixed;
- top: $header-height;
- left: 198px;
+ bottom: 0;
+ left: 0;
font-size: 13px;
background: transparent;
- width: 32px;
- height: 28px;
+ height: 40px;
text-align: center;
- line-height: 28px;
+ line-height: 40px;
transition-duration: .3s;
}
@@ -176,16 +208,18 @@
}
.sidebar-user {
+ padding: 9px 22px;
position: fixed;
- bottom: 0;
+ bottom: 40px;
width: $sidebar_width;
- padding: 10px;
overflow: hidden;
transition-duration: .3s;
.username {
- margin-top: 5px;
+ margin-left: 10px;
width: $sidebar_width - 2 * 10px;
+ font-size: 16px;
+ line-height: 34px;
}
}
@@ -202,7 +236,7 @@
float: left;
height: $header-height;
width: 100%;
- padding: ($header-height - 36 ) / 2 8px;
+ padding: 10px 22px;
overflow: hidden;
img {
@@ -219,8 +253,8 @@
float: left;
margin: 0;
margin-left: 14px;
- font-size: 18px;
- line-height: $header-height - 14;
+ font-size: 19px;
+ line-height: 41px;
font-weight: normal;
}
}
diff --git a/app/assets/stylesheets/generic/timeline.scss b/app/assets/stylesheets/generic/timeline.scss
index 97831eb7c27..74bbaabad39 100644
--- a/app/assets/stylesheets/generic/timeline.scss
+++ b/app/assets/stylesheets/generic/timeline.scss
@@ -1,119 +1,50 @@
.timeline {
- list-style: none;
- padding: 20px 0 20px;
- position: relative;
+ @include basic-list;
- &:before {
- top: 0;
- bottom: 0;
- position: absolute;
- content: " ";
- width: 3px;
- background-color: #eeeeee;
- margin-left: 29px;
- }
+ margin: 0;
+ padding: 0;
.timeline-entry {
- position: relative;
- margin-top: 5px;
- margin-left: 30px;
- margin-bottom: 10px;
- clear: both;
-
-
- &:target {
- .timeline-entry-inner .timeline-content {
- -webkit-animation:target-note 2s linear;
- background: $hover;
- }
+ padding: $gl-padding;
+ border-color: #f1f2f4;
+ margin-left: -$gl-padding;
+ margin-right: -$gl-padding;
+ color: $gl-gray;
+ border-bottom: 1px solid #f1f2f4;
+ border-right: 1px solid #f1f2f4;
+
+ &:last-child {
+ border-bottom: none;
}
- .timeline-entry-inner {
- position: relative;
- margin-left: -20px;
-
- &:before, &:after {
- content: " ";
- display: table;
- }
-
- .timeline-icon {
- margin-top: 2px;
- background: #fff;
- color: #737881;
- float: left;
- @include border-radius($avatar_radius);
- @include box-shadow(0 0 0 3px #EEE);
- overflow: hidden;
-
- .avatar {
- margin: 0;
- padding: 0;
- }
- }
-
- .timeline-content {
- position: relative;
- background: $background-color;
- padding: 10px 15px;
- margin-left: 60px;
-
- img {
- max-width: 100%;
- }
+ .avatar {
+ margin-right: 15px;
+ }
- &:after {
- content: '';
- display: block;
- position: absolute;
- width: 0;
- height: 0;
- border-style: solid;
- border-width: 9px 9px 9px 0;
- border-color: transparent $background-color transparent transparent;
- left: 0;
- top: 10px;
- margin-left: -9px;
- }
- }
+ .controls {
+ padding-top: 10px;
+ float: right;
}
}
- .system-note .timeline-entry-inner {
- .timeline-icon {
- background: none;
- margin-left: 12px;
- margin-top: 0;
- @include box-shadow(none);
-
- span {
- margin: 0 2px;
- font-size: 16px;
- color: #eeeeee;
- }
+ .note-text {
+ p:last-child {
+ margin-bottom: 0;
}
+ }
- .timeline-content {
- background: none;
- margin-left: 45px;
- padding: 0px 15px;
-
- &:after { border: 0; }
-
- .note-header {
- span { font-size: 12px; }
-
- .avatar {
- margin-right: 5px;
- }
- }
-
- .note-text {
- font-size: 12px;
- margin-left: 20px;
- }
+ .system-note {
+ .note-text {
+ color: $gl-gray !important;
}
}
+
+ .diff-file {
+ border: 1px solid $border-color;
+ border-bottom: none;
+ margin-left: 0;
+ margin-right: 0;
+ }
}
@media (max-width: $screen-xs-max) {
@@ -132,3 +63,8 @@
}
}
}
+
+.discussion .timeline-entry {
+ margin: 0;
+ border-right: none;
+}
diff --git a/app/assets/stylesheets/generic/typography.scss b/app/assets/stylesheets/generic/typography.scss
index 34b4ee3e17e..6a3cb49baae 100644
--- a/app/assets/stylesheets/generic/typography.scss
+++ b/app/assets/stylesheets/generic/typography.scss
@@ -2,11 +2,29 @@
* Headers
*
*/
+body {
+ text-rendering:optimizeLegibility;
+ -webkit-text-shadow: rgba(255,255,255,0.01) 0 0 1px;
+}
+
.page-title {
margin-top: 0px;
- line-height: 1.5;
- font-weight: normal;
- margin-bottom: 5px;
+ line-height: 1.3;
+ font-size: 1.25em;
+ font-weight: 600;
+}
+
+.page-title-empty {
+ margin-top: 0px;
+ line-height: 1.3;
+ font-size: 1.25em;
+ font-weight: 600;
+ margin: 12px 7px 12px 7px;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ color: $gl-header-color;
+ font-weight: 500;
}
/** CODE **/
@@ -50,6 +68,7 @@ a > code {
@include md-typography;
word-wrap: break-word;
+ padding: 7px;
/* Link to current header. */
h1, h2, h3, h4, h5, h6 {
@@ -78,12 +97,19 @@ a > code {
}
}
- ul {
+ ul,ol {
padding: 0;
- margin: 0 0 9px 25px !important;
+ margin: 6px 0 6px 18px !important;
+ }
+ ol {
+ color: #5c5d5e;
}
}
+.md-area {
+ @include md-typography;
+}
+
.md {
@include md-typography;
}
@@ -96,6 +122,9 @@ textarea.js-gfm-input {
font-family: $monospace_font;
}
+.md-preview {
+}
+
.strikethrough {
text-decoration: line-through;
-}
+} \ No newline at end of file
diff --git a/app/assets/stylesheets/generic/zen.scss b/app/assets/stylesheets/generic/zen.scss
index 7e86a0fe4b9..32e2c020e06 100644
--- a/app/assets/stylesheets/generic/zen.scss
+++ b/app/assets/stylesheets/generic/zen.scss
@@ -4,7 +4,7 @@
}
.zen-enter-link {
- color: #888;
+ color: $gl-gray;
position: absolute;
top: 0px;
right: 4px;
@@ -13,7 +13,7 @@
.zen-leave-link {
display: none;
- color: #888;
+ color: $gl-text-color;
position: absolute;
top: 10px;
right: 10px;
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index e0edfb80b42..20a144ef952 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -1,19 +1,24 @@
/* https://github.com/aahan/pygments-github-style */
pre.code.highlight.white,
.code.white {
+ background-color: #f8fafc;
+ font-size: 13px;
+ color: #5b6169;
+ line-height: 1.6em;
- background-color: #fff;
- color: #333;
-
- pre.highlight,
.line-numbers,
.line-numbers a {
+ background-color: $background-color !important;
+ color: $gl-gray !important;
+ }
+
+ pre.highlight {
background-color: #fff !important;
color: #333 !important;
}
pre.code {
- border-left: 1px solid #bbb;
+ border-left: 1px solid $border-color;
}
// highlight line via anchor
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
index e7125c03993..fbd7c363de1 100644
--- a/app/assets/stylesheets/pages/commit.scss
+++ b/app/assets/stylesheets/pages/commit.scss
@@ -26,14 +26,6 @@
margin-top: 10px;
}
-.commit-stat-summary {
- color: #666;
- font-size: 14px;
- font-weight: normal;
- padding: 3px 0;
- margin-bottom: 10px;
-}
-
.commit-info-row {
margin-bottom: 10px;
.avatar {
@@ -47,11 +39,6 @@
}
.commit-box {
- margin: 10px 0;
- border-top: 1px solid #ddd;
- border-bottom: 1px solid #ddd;
- padding: 20px 0;
-
.commit-title {
margin: 0;
}
@@ -61,35 +48,34 @@
}
}
-.file-stats a {
- color: $style_color;
-}
-
.file-stats {
+ ul {
+ list-style: none;
+ margin: 0;
+ padding: 10px 0;
+
+ li {
+ padding: 3px 0px;
+ }
+ }
.new-file {
a {
- color: #090;
- }
- i {
- color: #1BCF00;
+ color: $gl-text-green;
}
}
.renamed-file {
- i {
- color: #FE9300;
+ a {
+ color: $gl-text-orange;
}
}
.deleted-file {
a {
- color: #B00;
- }
- i {
- color: #EE0000;
+ color: $gl-text-red;
}
}
.edit-file{
- i{
- color: #555;
+ a {
+ color: $gl-text-color;
}
}
}
@@ -121,3 +107,16 @@
z-index: 2;
}
}
+
+.commit-ci-menu {
+ padding: 0;
+ margin: 0;
+ list-style: none;
+ margin-top: 5px;
+ height: 56px;
+ margin: -16px;
+ padding: 16px;
+ text-align: center;
+ margin-top: 0px;
+ margin-bottom: 2px;
+}
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 359f4073e87..de2ae93df37 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -52,7 +52,7 @@ li.commit {
}
.commit-row-message {
- color: #444;
+ color: $gl-link-color;
&:hover {
text-decoration: underline;
@@ -88,12 +88,12 @@ li.commit {
}
.commit-row-info {
- color: #777;
+ color: $gl-gray;
line-height: 24px;
font-size: 13px;
a {
- color: #777;
+ color: $gl-gray;
}
.committed_ago {
diff --git a/app/assets/stylesheets/pages/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss
index c1103a1c2e6..25a86cd0f94 100644
--- a/app/assets/stylesheets/pages/dashboard.scss
+++ b/app/assets/stylesheets/pages/dashboard.scss
@@ -2,7 +2,7 @@
.side {
.panel {
.panel-heading {
- background: #EEE;
+ background: $background-color;
border-top-left-radius: 0;
}
border-top-left-radius: 0;
@@ -38,11 +38,11 @@
float: left;
.avatar {
- @include border-radius(0px);
+ @include border-radius(50%);
}
.identicon {
- line-height: 40px;
+ line-height: 46px;
}
}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 1557c243db5..5e7e59a6af8 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -1,12 +1,14 @@
.diff-file {
- border: 1px solid $border-color;
- margin-bottom: 1em;
+ margin-left: -$gl-padding;
+ margin-right: -$gl-padding;
+ border: none;
+ border-bottom: 1px solid #E7E9EE;
.diff-header {
position: relative;
background: $background-color;
border-bottom: 1px solid $border-color;
- padding: 10px 15px;
+ padding: 10px 16px;
color: #555;
z-index: 10;
@@ -45,7 +47,7 @@
overflow-y: hidden;
background: #FFF;
color: #333;
- font-size: $code_font_size;
+
.old {
span.idiff {
background-color: #f8cbcb;
@@ -82,7 +84,7 @@
border: none;
margin: 0px;
padding: 0px;
- td {
+ .line_holder td {
line-height: $code_line_height;
font-size: $code_font_size;
}
@@ -367,3 +369,7 @@
white-space: pre-wrap;
}
+.inline-parallel-buttons {
+ float: right;
+ margin-top: -5px;
+}
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index 759ba6b1c22..1d565477dd4 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -9,6 +9,10 @@
width: 100%;
}
+ .ace_gutter-cell {
+ background-color: $background-color;
+ }
+
.cancel-btn {
color: #B94A48;
&:hover {
@@ -32,14 +36,12 @@
.file-title {
@extend .monospace;
- font-size: 14px;
- padding: 5px;
}
.editor-ref {
background: $background-color;
padding: 11px 15px;
- border-right: 1px solid #CCC;
+ border-right: 1px solid $border-color;
display: inline-block;
margin: -5px -5px;
margin-right: 10px;
@@ -50,5 +52,15 @@
display: inline-block;
width: 200px;
}
+
+ .form-control {
+ margin-top: -3px;
+ }
+ }
+
+ .form-actions {
+ margin: -$gl-padding;
+ margin-top: 0;
+ padding: $gl-padding
}
}
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index d4af7506d5b..ca2ee455423 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -1,70 +1,58 @@
/**
- * Events labels
- *
- */
-.event_label {
- &.pushed {
- padding: 0 2px;
- }
-
- &.opened {
- padding: 0 2px;
- }
-
- &.closed {
- padding: 0 2px;
- }
-
- &.merged {
- padding: 0 2px;
- }
-
- &.left,
- &.joined {
- padding: 0 2px;
- float: none;
- }
-}
-
-/**
* Dashboard events feed
*
*/
.event-item {
- &:first-child {
- padding-top: 0;
- }
+ font-size: $gl-font-size;
+ padding: $gl-padding;
+ margin-left: -$gl-padding;
+ margin-right: -$gl-padding;
+ border-bottom: 1px solid #f1f2f4;
+ color: #7f8fa4;
&.event-inline {
.avatar {
position: relative;
top: -2px;
}
+
+ .event-title {
+ line-height: 44px;
+ }
+
+ .event-item-timestamp {
+ line-height: 44px;
+ }
+ }
+
+ a {
+ color: #4c4e54;
+ }
+
+ .avatar {
+ margin-right: 15px;
}
- padding: 12px 0px;
- border-bottom: 1px solid #eee;
.event-title {
- max-width: 70%;
@include str-truncated(calc(100% - 174px));
- font-weight: 500;
- font-size: 14px;
+ font-weight: 600;
+
.author_name {
color: #333;
}
}
+
.event-body {
- font-size: 13px;
- margin-left: 35px;
+ margin-left: 63px;
margin-right: 80px;
- color: #777;
.event-note {
margin-top: 5px;
word-wrap: break-word;
.md {
- font-size: 13px;
+ color: #7f8fa4;
+ font-size: $gl-font-size;
iframe.twitter-share-button {
vertical-align: bottom;
@@ -94,7 +82,7 @@
.event-note-icon {
color: #777;
float: left;
- font-size: 16px;
+ font-size: $gl-font-size;
line-height: 16px;
margin-right: 5px;
}
@@ -116,7 +104,7 @@
&:last-child { border:none }
.event_commits {
- margin-top: 5px;
+ margin-top: 9px;
li {
&.commit {
@@ -125,10 +113,12 @@
padding-left: 0;
border: none;
.commit-row-title {
- font-size: 12px;
+ font-size: $gl-font-size;
}
}
+
&.commits-stat {
+ margin-top: 3px;
display: block;
padding: 3px;
padding-left: 0;
@@ -142,7 +132,6 @@
.event-item-timestamp {
float: right;
- color: #999;
line-height: 22px;
}
}
@@ -186,12 +175,3 @@
}
}
}
-
-.event_filter {
- li a {
- font-size: 13px;
- padding: 5px 10px;
- background: $background-color;
- margin-left: 4px;
- }
-}
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 2b1b747139a..07a38a19fad 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -10,3 +10,9 @@
.milestone-row {
@include str-truncated(90%);
}
+
+.dashboard .side .panel .panel-heading .input-group {
+ .form-control {
+ height: 42px;
+ }
+} \ No newline at end of file
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 586e7b5f8da..b5c61f7f91d 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -25,8 +25,6 @@
}
.issuable-context-title {
- font-size: 14px;
- line-height: 1.4;
margin-bottom: 5px;
.avatar {
@@ -34,14 +32,50 @@
}
label {
- color: #666;
+ color: $gl-gray;
font-weight: normal;
margin-right: 4px;
}
}
-.issuable-affix .context {
- font-size: 13px;
+.project-issuable-filter {
+ .controls {
+ float: right;
+ margin-top: 7px;
+ }
+
+ .center-top-menu {
+ text-align: left;
+ }
+}
- .btn { font-size: 13px; }
+.issuable-details {
+ .page-title {
+ margin-top: -15px;
+ padding: 10px 0;
+ margin-bottom: 0;
+ color: $gl-gray;
+ font-size: 16px;
+
+ .author {
+ color: $gl-gray;
+ }
+
+ .issue-id {
+ font-size: 19px;
+ color: $gl-text-color;
+ }
+ }
+
+ .issue-title {
+ margin: 0;
+ }
+
+ .description {
+ margin-top: 6px;
+
+ p:last-child {
+ margin-bottom: 0;
+ }
+ }
}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 3572f33e91f..4bf58cb4a59 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -1,6 +1,6 @@
.issues-list {
.issue {
- padding: 10px 15px;
+ padding: 10px $gl-padding;
position: relative;
.issue-title {
@@ -10,8 +10,7 @@
}
.issue-info {
- color: #999;
- font-size: 13px;
+ color: $gl-gray;
}
.issue-check {
@@ -47,10 +46,6 @@
}
}
-.participants {
- margin-bottom: 20px;
-}
-
.issue-search-form {
margin: 0;
height: 24px;
@@ -137,11 +132,6 @@ form.edit-issue {
}
}
-h2.issue-title {
- margin-top: 0;
- font-weight: bold;
-}
-
.issue-form .select2-container {
width: 250px !important;
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 10fce5b3daa..d8c8e5ad0a4 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -3,10 +3,10 @@
*
*/
.mr-state-widget {
- background: #FAFAFA;
+ background: #f8fafc;
margin-bottom: 20px;
- color: #666;
- border: 1px solid #e5e5e5;
+ color: $gl-gray;
+ border: 1px solid #eef0f2;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.05));
@include border-radius(3px);
@@ -29,6 +29,14 @@
padding: 5px;
line-height: 20px;
+ &.right {
+ float: right;
+ padding-top: 12px;
+ a {
+ color: $gl-gray;
+ }
+ }
+
.remove_source_checkbox {
margin: 0;
}
@@ -36,7 +44,7 @@
}
.ci_widget {
- border-bottom: 1px solid #EEE;
+ border-bottom: 1px solid #eef0f2;
i {
margin-right: 4px;
@@ -89,20 +97,14 @@
}
}
-@media(min-width: $screen-sm-max) {
- .merge-request .merge-request-tabs{
- li {
- a {
- padding: 15px 40px;
- font-size: 14px;
- }
- }
- }
-}
-
.merge-request .merge-request-tabs{
- margin-top: 30px;
- margin-bottom: 20px;
+ @include nav-menu;
+ margin: -$gl-padding;
+ padding: $gl-padding;
+ text-align: center;
+ border-top: 1px solid #e7e9ed;
+ margin-top: 18px;
+ margin-bottom: 3px;
}
.mr_source_commit,
@@ -136,8 +138,7 @@
}
.merge-request-info {
- color: #999;
- font-size: 13px;
+ color: $gl-gray;
}
}
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index 15e3948e402..e80dc9e84a1 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -6,4 +6,8 @@ li.milestone {
h4 {
font-weight: bold;
}
+
+ .progress {
+ height: 6px;
+ }
}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 203f9374cee..fdc2c3332df 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -72,9 +72,13 @@
.common-note-form {
margin: 0;
- background: #F9F9F9;
- padding: 5px;
- border: 1px solid #DDD;
+ background: #f8fafc;
+ padding: $gl-padding;
+ margin-left: -$gl-padding;
+ margin-right: -$gl-padding;
+ border-right: 1px solid #f1f2f4;
+ border-top: 1px solid #f1f2f4;
+ margin-bottom: -$gl-padding;
}
.note-form-actions {
@@ -105,7 +109,7 @@
.note-edit-form {
display: none;
- font-size: 13px;
+ font-size: 15px;
.form-actions {
padding-left: 20px;
@@ -142,9 +146,9 @@
}
.discussion-reply-holder {
- background: #f9f9f9;
+ background: $background-color;
padding: 10px 15px;
- border-top: 1px solid #DDD;
+ border-top: 1px solid $border-color;
}
}
@@ -166,6 +170,6 @@
background: #FFF;
padding: 5px;
margin-top: -11px;
- border: 1px solid #DDD;
+ border: 1px solid $border-color;
font-size: 13px;
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 85c828ec1ad..2a77f065aed 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -14,6 +14,19 @@ ul.notes {
margin: 0px;
padding: 0px;
+ .system-note {
+ font-size: 14px;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ background: #f8fafc;
+
+ .timeline-icon {
+ .avatar {
+ visibility: hidden;
+ }
+ }
+ }
+
.discussion-header,
.note-header {
@extend .cgray;
@@ -34,10 +47,8 @@ ul.notes {
content: "\00b7";
}
- font-size: 13px;
-
a {
- @extend .cgray;
+ color: $gl-gray;
&:hover {
text-decoration: underline;
@@ -45,8 +56,9 @@ ul.notes {
}
}
.author {
- color: #333;
- font-weight: bold;
+ color: #4c4e54;
+ margin-right: 3px;
+
&:hover {
color: $gl-link-color;
}
@@ -59,7 +71,7 @@ ul.notes {
margin-top: 1px;
border: 1px solid #bbb;
background-color: transparent;
- color: #999;
+ color: $gl-gray;
}
}
@@ -133,8 +145,6 @@ ul.notes {
}
.diff-file .notes_holder {
- font-size: 13px;
- line-height: 18px;
font-family: $regular_font;
td {
@@ -176,8 +186,7 @@ ul.notes {
a {
margin-left: 5px;
-
- color: #999;
+ color: $gl-gray;
i.fa {
font-size: 16px;
@@ -226,8 +235,6 @@ ul.notes {
filter: alpha(opacity=0);
&:hover {
- width: 38px;
- font-size: 20px;
background: $gl-info;
color: #FFF;
@include show-add-diff-note;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 488dded549e..818aa10aefe 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -1,3 +1,14 @@
+.alert_holder {
+ margin: -16px;
+
+ .alert-link {
+ font-weight: normal;
+ }
+}
+.no-ssh-key-message {
+ background-color: #f28d35;
+ margin-bottom: 16px;
+}
.new_project,
.edit_project {
fieldset.features {
@@ -16,9 +27,13 @@
.project-home-panel {
text-align: center;
+ background: #f7f8fa;
+ margin: -$gl-padding;
+ padding: $gl-padding;
+ padding: 44px 0 17px 0;
.project-identicon-holder {
- margin-bottom: 15px;
+ margin-bottom: 16px;
.avatar, .identicon {
margin: 0 auto;
@@ -36,22 +51,27 @@
.project-home-desc {
h1 {
+ color: #313236;
margin: 0;
- margin-bottom: 10px;
- font-size: 26px;
- font-weight: bold;
+ margin-bottom: 6px;
+ font-size: 23px;
+ font-weight: normal;
}
p {
- font-size: 18px;
- color: #666;
- display: inline;
+ color: #5c5d5e;
}
}
.git-clone-holder {
- max-width: 600px;
- margin: 20px auto;
+ max-width: 498px;
+
+ .form-control {
+ background: #FFF;
+ font-size: 14px;
+ height: 42px;
+ margin-left: -1px;
+ }
}
.visibility-level-label {
@@ -60,29 +80,37 @@
color: inherit;
}
}
+ .input-group {
+ display: inline-table;
+ position: relative;
+ top: 17px;
+ margin-bottom: 44px;
+ }
.project-repo-buttons {
- margin-top: 25px;
- margin-bottom: 25px;
+ margin-top: 12px;
+ margin-bottom: 0px;
.btn {
- @extend .btn-info;
-
- margin-left: 10px;
- font-weight: bold;
- font-size: 14px;
- line-height: 16px;
- padding: 8px 12px;
+ @include bnt-project;
+ @include btn-info;
.count {
- padding-left: 7px;
display: inline-block;
- margin-left: 7px;
}
}
}
}
+.split-one {
+ display: inline-table;
+ margin-right: 12px;
+
+ a {
+ margin: -1px !important;
+ }
+}
+
.git-clone-holder {
.project-home-dropdown + & {
margin-right: 45px;
@@ -92,11 +120,11 @@
cursor: auto;
@extend .monospace;
background: #FAFAFA;
- width: 100%;
+ width: 101%;
}
.input-group-addon {
- background: #FAFAFA;
+ background: #f7f8fa;
&.git-protocols {
padding: 0;
@@ -104,11 +132,120 @@
.input-group-btn:last-child > .btn {
@include border-radius-right(0);
+
+ border-left: 1px solid #c6cacf;
+ margin-left: -2px !important;
}
}
}
}
+.projects-search-form {
+
+ .input-group .form-control {
+ height: 42px;
+ }
+}
+
+.input-group-btn {
+ .btn {
+ @include bnt-project;
+ @include btn-middle;
+
+ &:hover {
+ outline: none;
+ }
+
+ &:focus {
+ outline: none;
+ }
+
+ &:active {
+ outline: none;
+ }
+ }
+
+ .active {
+ @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
+
+ border: 1px solid #c6cacf !important;
+ background-color: #e4e7ed !important;
+ }
+
+ .btn-green {
+ @include btn-green
+ }
+
+}
+
+.split-repo-buttons {
+ display: inline-table;
+ margin: 0 12px 0 12px;
+
+ .btn{
+ @include bnt-project;
+ @include btn-info;
+ }
+
+ .dropdown-toggle {
+ margin: -5px;
+ }
+}
+
+#notification-form {
+ margin-left: 5px;
+}
+
+.dropdown-new {
+ margin-left: -5px;
+}
+
+.open > .dropdown-new.btn {
+ @include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12));
+
+ border: 1px solid #c6cacf !important;
+ background-color: #e4e7ed !important;
+ text-transform: uppercase;
+ color: #313236 !important;
+ font-size: 13px;
+ font-weight: 600;
+}
+
+.dropdown-menu {
+ @include box-shadow(rgba(76, 86, 103, 0.247059) 0px 0px 1px 0px, rgba(31, 37, 50, 0.317647) 0px 2px 18px 0px);
+ @include border-radius (0px);
+
+ border: none;
+ padding: 16px 0;
+ font-size: 14px;
+ font-weight: 100;
+
+ li a {
+ color: #5f697a;
+ line-height: 30px;
+
+ &:hover {
+ background-color: #3084bb !important;
+ }
+ }
+
+ .fa-fw {
+ margin-right: 8px;
+ }
+}
+
+.fa-bell {
+ margin-right: 6px;
+}
+
+.fa-angle-down {
+ margin-left: 6px;
+}
+
+.project-home-panel .project-home-dropdown {
+ margin: 13px 0px 0;
+}
+
.project-visibility-level-holder {
.radio {
margin-bottom: 10px;
@@ -184,10 +321,10 @@ ul.nav.nav-projects-tabs {
.breadcrumb.repo-breadcrumb {
padding: 0;
- line-height: 34px;
- background: white;
+ line-height: 42px;
+ background: transparent;
border: none;
- font-size: 16px;
+ margin: 0;
> li + li:before {
padding: 0 3px;
@@ -196,26 +333,18 @@ ul.nav.nav-projects-tabs {
}
.fork-namespaces {
- .thumbnail {
-
- &.fork-exists-thumbnail {
- border-color: #EEE;
+ .fork-thumbnail {
+ text-align: center;
+ margin-bottom: $gl-padding;
- .caption {
- color: #999;
- }
+ .caption {
+ padding: $gl-padding 0;
+ min-height: 30px;
}
- &.fork-thumbnail {
- border-color: #AAA;
-
- &:hover {
- background-color: $hover;
- }
- }
-
- a {
- text-decoration: none;
+ img {
+ @include border-radius(50%);
+ max-width: 100px;
}
}
}
@@ -233,17 +362,43 @@ table.table.protected-branches-list tr.no-border {
.project-stats {
text-align: center;
+ margin-top: 15px;
+ margin-bottom: 0;
+ padding-top: 10px;
+ padding-bottom: 4px;
+
+ ul.nav-pills {
+ display:inline-block;
+ }
+
+ .nav-pills li {
+ display:inline;
+ }
+
+ .nav > li > a {
+ @include btn-info;
+ @include bnt-project;
- ul.nav-pills { display:inline-block; }
- li { display:inline; }
- a { float:left; }
+ background-color: transparent;
+ border: 1px solid #f7f8fa;
+ margin-left: 12px;
+ }
+
+ li {
+ display:inline;
+ }
+
+ a {
+ float:left;
+ font-size: 17px;
+ }
li.missing a {
- color: #bbb;
- border: 1px dashed #ccc;
+ color: #5a6069;
+ border: 1px dashed #dce0e5;
&:hover {
- background-color: #FAFAFA;
+ background-color: #f0f2f5;
}
}
}
@@ -253,41 +408,106 @@ pre.light-well {
}
.projects-search-form {
- max-width: 600px;
- margin: 0 auto;
- margin-bottom: 20px;
+ margin: -$gl-padding;
+ background-color: #f8fafc;
+ padding: $gl-padding;
+ margin-bottom: 0px;
+ border-top: 1px solid #e7e9ed;
+ border-bottom: 1px solid #e7e9ed;
+}
+
+.git-empty {
+ margin: 0 7px 0 7px;
+
+ h5 {
+ color: #5c5d5e;
+ }
+
+ .light-well {
+ @include border-radius (2px);
+
+ color: #5b6169;
+ font-size: 13px;
+ line-height: 1.6em;
+ }
+}
+
+.project-footer {
+ margin-top: 20px;
+
+ .btn-remove {
+ @include btn-middle;
+ @include btn-remove;
- input {
- border-color: #BBB;
+ float: left !important;
}
}
/*
* Projects list rendered on dashboard and user page
*/
+
.projects-list {
@include basic-list;
.project-row {
+ padding: $gl-padding;
+ border-color: #f1f2f4;
+ margin-left: -$gl-padding;
+ margin-right: -$gl-padding;
+
+ &.no-description {
+ .project {
+ line-height: 44px;
+ }
+ }
+
.project-full-name {
@include str-truncated;
- font-weight: bold;
- font-size: 15px;
+ font-weight: 600;
+ color: #4c4e54;
+ }
+
+ .project-controls {
+ float: right;
+ color: $gl-gray;
+ line-height: 45px;
+ color: #7f8fa4;
+
+ a:hover {
+ text-decoration: none;
+ }
}
.project-description {
- color: #888;
- font-size: 13px;
+ color: #7f8fa4;
p {
@include str-truncated;
margin-bottom: 0;
- color: #888;
+ color: #7f8fa4;
}
}
}
+
+ .bottom {
+ padding-top: $gl-padding;
+ padding-bottom: 0;
+ }
}
.panel .projects-list li {
padding: 10px 15px;
+ margin: 0;
+}
+
+.project-show-activity {
+ .activity-filter-block {
+ margin-top: -1px;
+ }
}
+
+.inline-form {
+ display: inline-block;
+}
+
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 81e2aa7bb9c..271cc547e2b 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -63,51 +63,21 @@
padding-right: 8px;
.commit-author-name {
- color: gray;
+ color: $gl-gray;
}
}
.tree_commit {
- color: gray;
+ color: $gl-gray;
.tree-commit-link {
- color: gray;
+ color: $gl-gray;
&:hover {
text-decoration: underline;
}
}
}
-
- .blame {
- img.avatar {
- border: 0 none;
- float: none;
- margin: 0;
- padding: 0;
- }
- td.blame-commit {
- background: #f9f9f9;
- min-width: 350px;
-
- .commit-author-link {
- color: #888;
- }
- }
- td.blame-numbers {
- pre {
- color: #AAA;
- white-space: pre;
- }
- background: #f1f1f1;
- border-left: 1px solid #DDD;
- }
- td.lines {
- code {
- font-family: $monospace_font;
- }
- }
- }
}
.tree-ref-holder {
@@ -132,20 +102,30 @@
list-style: none;
margin: 0;
padding: 0;
- margin-bottom: 10px;
+ margin-bottom: 5px;
.commit {
- padding: 10px 15px;
+ padding: $gl-padding 0;
.commit-row-title {
- font-size: 13px;
-
.commit-row-message {
font-weight: normal;
- color: #555;
}
}
}
}
#modal-remove-blob > .modal-dialog { width: 850px; }
+
+.blob-upload-dropzone-previews {
+ text-align: center;
+ border: 2px;
+ border-style: dashed;
+ border-color: $border-color;
+ min-height: 200px;
+}
+
+.upload-link {
+ font-weight: normal;
+ color: $md-link-color;
+}
diff --git a/app/assets/stylesheets/themes/gitlab-theme.scss b/app/assets/stylesheets/themes/gitlab-theme.scss
index 77b62c3153f..8d9a0aae568 100644
--- a/app/assets/stylesheets/themes/gitlab-theme.scss
+++ b/app/assets/stylesheets/themes/gitlab-theme.scss
@@ -9,15 +9,19 @@
@mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
.page-with-sidebar {
.header-logo {
- background-color: $color-darker;
- border-color: $color-darker;
+ background-color: $color;
+ border-color: $color;
a {
color: $color-light;
+
+ h3 {
+ color: $color-light;
+ }
}
&:hover {
- background-color: $color-dark;
+ background-color: $color-darker;
a {
color: #FFF;
}
@@ -83,7 +87,7 @@
}
$theme-blue: #2980B9;
-$theme-charcoal: #474D57;
+$theme-charcoal: #333c47;
$theme-graphite: #888888;
$theme-gray: #373737;
$theme-green: #019875;
@@ -95,7 +99,7 @@ body {
}
&.ui_charcoal {
- @include gitlab-theme(#979DA7, $theme-charcoal, #373D47, #24272D);
+ @include gitlab-theme(#c5d0de, $theme-charcoal, #2b333d, #24272D);
}
&.ui_graphite {
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index f38e07af84b..7c134d2ec9b 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -46,6 +46,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:gravatar_enabled,
:twitter_sharing_enabled,
:sign_in_text,
+ :help_page_text,
:home_page_url,
:after_sign_out_path,
:max_attachment_size,
diff --git a/app/controllers/admin/labels_controller.rb b/app/controllers/admin/labels_controller.rb
new file mode 100644
index 00000000000..3b070e65d0d
--- /dev/null
+++ b/app/controllers/admin/labels_controller.rb
@@ -0,0 +1,58 @@
+class Admin::LabelsController < Admin::ApplicationController
+ before_action :set_label, only: [:show, :edit, :update, :destroy]
+
+ def index
+ @labels = Label.templates.page(params[:page]).per(PER_PAGE)
+ end
+
+ def show
+ end
+
+ def new
+ @label = Label.new
+ end
+
+ def edit
+ end
+
+ def create
+ @label = Label.new(label_params)
+ @label.template = true
+
+ if @label.save
+ redirect_to admin_labels_url, notice: "Label was created"
+ else
+ render :new
+ end
+ end
+
+ def update
+ if @label.update(label_params)
+ redirect_to admin_labels_path, notice: 'label was successfully updated.'
+ else
+ render :edit
+ end
+ end
+
+ def destroy
+ @label.destroy
+ @labels = Label.templates
+
+ respond_to do |format|
+ format.html do
+ redirect_to(admin_labels_path, notice: 'Label was removed')
+ end
+ format.js
+ end
+ end
+
+ private
+
+ def set_label
+ @label = Label.find(params[:id])
+ end
+
+ def label_params
+ params[:label].permit(:title, :color)
+ end
+end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 6092c79c254..00f41a10dd1 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -56,13 +56,19 @@ class Admin::UsersController < Admin::ApplicationController
end
def confirm
- if user.confirm!
+ if user.confirm
redirect_to :back, notice: "Successfully confirmed"
else
redirect_to :back, alert: "Error occurred. User was not confirmed"
end
end
+ def login_as
+ sign_in(user)
+ flash[:alert] = "Logged in as #{user.username}"
+ redirect_to root_path
+ end
+
def disable_two_factor
user.disable_two_factor!
redirect_to admin_user_path(user),
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index cb1cf13d34d..527c9da0faa 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,4 +1,5 @@
require 'gon'
+require 'fogbugz'
class ApplicationController < ActionController::Base
include Gitlab::CurrentSettings
@@ -20,7 +21,7 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
helper_method :abilities, :can?, :current_application_settings
- helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :git_import_enabled?
+ helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?
rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception)
@@ -116,9 +117,14 @@ class ApplicationController < ActionController::Base
redirect_to request.original_url.gsub(/\.git\Z/, '') and return
end
- @project = Project.find_with_namespace("#{namespace}/#{id}")
+ project_path = "#{namespace}/#{id}"
+ @project = Project.find_with_namespace(project_path)
+
if @project and can?(current_user, :read_project, @project)
+ if @project.path_with_namespace != project_path
+ redirect_to request.original_url.gsub(project_path, @project.path_with_namespace) and return
+ end
@project
elsif current_user.nil?
@project = nil
@@ -133,9 +139,6 @@ class ApplicationController < ActionController::Base
def repository
@repository ||= project.repository
- rescue Grit::NoSuchPathError => e
- log_exception(e)
- nil
end
def authorize_project!(action)
@@ -337,6 +340,10 @@ class ApplicationController < ActionController::Base
current_application_settings.import_sources.include?('google_code')
end
+ def fogbugz_import_enabled?
+ current_application_settings.import_sources.include?('fogbugz')
+ end
+
def git_import_enabled?
current_application_settings.import_sources.include?('git')
end
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 904d26a39f4..202e9da9eee 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -32,6 +32,7 @@ class AutocompleteController < ApplicationController
@users ||= User.none
@users = @users.search(params[:search]) if params[:search].present?
@users = @users.active
+ @users = @users.reorder(:name)
@users = @users.page(params[:page]).per(PER_PAGE)
unless params[:search].present?
diff --git a/app/controllers/ci/admin/application_controller.rb b/app/controllers/ci/admin/application_controller.rb
new file mode 100644
index 00000000000..4ec2dc9c2cf
--- /dev/null
+++ b/app/controllers/ci/admin/application_controller.rb
@@ -0,0 +1,10 @@
+module Ci
+ module Admin
+ class ApplicationController < Ci::ApplicationController
+ before_action :authenticate_user!
+ before_action :authenticate_admin!
+
+ layout "ci/admin"
+ end
+ end
+end
diff --git a/app/controllers/ci/admin/application_settings_controller.rb b/app/controllers/ci/admin/application_settings_controller.rb
new file mode 100644
index 00000000000..71e253fac67
--- /dev/null
+++ b/app/controllers/ci/admin/application_settings_controller.rb
@@ -0,0 +1,31 @@
+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
diff --git a/app/controllers/ci/admin/builds_controller.rb b/app/controllers/ci/admin/builds_controller.rb
new file mode 100644
index 00000000000..38abfdeafbf
--- /dev/null
+++ b/app/controllers/ci/admin/builds_controller.rb
@@ -0,0 +1,18 @@
+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
diff --git a/app/controllers/ci/admin/events_controller.rb b/app/controllers/ci/admin/events_controller.rb
new file mode 100644
index 00000000000..5939efff980
--- /dev/null
+++ b/app/controllers/ci/admin/events_controller.rb
@@ -0,0 +1,9 @@
+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
diff --git a/app/controllers/ci/admin/projects_controller.rb b/app/controllers/ci/admin/projects_controller.rb
new file mode 100644
index 00000000000..5bbd0ce7396
--- /dev/null
+++ b/app/controllers/ci/admin/projects_controller.rb
@@ -0,0 +1,19 @@
+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
diff --git a/app/controllers/ci/admin/runner_projects_controller.rb b/app/controllers/ci/admin/runner_projects_controller.rb
new file mode 100644
index 00000000000..e7de6eb12ca
--- /dev/null
+++ b/app/controllers/ci/admin/runner_projects_controller.rb
@@ -0,0 +1,34 @@
+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
diff --git a/app/controllers/ci/admin/runners_controller.rb b/app/controllers/ci/admin/runners_controller.rb
new file mode 100644
index 00000000000..9a68add9083
--- /dev/null
+++ b/app/controllers/ci/admin/runners_controller.rb
@@ -0,0 +1,72 @@
+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
+ if params[:search].present?
+ @gl_projects = ::Project.search(params[:search])
+ @projects = @projects.where(gitlab_id: @gl_projects.select(:id))
+ end
+ @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
diff --git a/app/controllers/ci/application_controller.rb b/app/controllers/ci/application_controller.rb
new file mode 100644
index 00000000000..9be470660e6
--- /dev/null
+++ b/app/controllers/ci/application_controller.rb
@@ -0,0 +1,74 @@
+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
+ authenticate_user!
+
+ 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, :manage_builds, 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
diff --git a/app/controllers/ci/builds_controller.rb b/app/controllers/ci/builds_controller.rb
new file mode 100644
index 00000000000..b0b8b62fced
--- /dev/null
+++ b/app/controllers/ci/builds_controller.rb
@@ -0,0 +1,52 @@
+module Ci
+ class BuildsController < Ci::ApplicationController
+ before_action :authenticate_user!, except: [:status]
+ before_action :project
+ before_action :authorize_access_project!, except: [:status]
+ before_action :authorize_manage_project!, except: [:status, :retry, :cancel]
+ before_action :authorize_manage_builds!, only: [:retry, :cancel]
+ before_action :build
+
+ 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 build_path(build)
+ end
+ end
+
+ def status
+ render json: @build.to_json(only: [:status, :id, :sha, :coverage], methods: :sha)
+ end
+
+ def cancel
+ @build.cancel
+
+ redirect_to build_path(@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
+
+ def build_path(build)
+ namespace_project_build_path(build.gl_project.namespace, build.gl_project, build)
+ end
+ end
+end
diff --git a/app/controllers/ci/commits_controller.rb b/app/controllers/ci/commits_controller.rb
new file mode 100644
index 00000000000..7e6705c9702
--- /dev/null
+++ b/app/controllers/ci/commits_controller.rb
@@ -0,0 +1,32 @@
+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]
+
+ def status
+ commit = Ci::Project.find(params[:project_id]).commits.find_by_sha!(params[: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 namespace_project_commit_path(commit.gl_project.namespace, commit.gl_project, 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!(params[:id])
+ end
+ end
+end
diff --git a/app/controllers/ci/events_controller.rb b/app/controllers/ci/events_controller.rb
new file mode 100644
index 00000000000..89b784a1e89
--- /dev/null
+++ b/app/controllers/ci/events_controller.rb
@@ -0,0 +1,21 @@
+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
diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb
new file mode 100644
index 00000000000..24dd1b5c93a
--- /dev/null
+++ b/app/controllers/ci/lints_controller.rb
@@ -0,0 +1,26 @@
+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
+ @error = "Undefined error"
+ @status = false
+ end
+ end
+end
diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb
new file mode 100644
index 00000000000..adb913a783f
--- /dev/null
+++ b/app/controllers/ci/projects_controller.rb
@@ -0,0 +1,56 @@
+module Ci
+ class ProjectsController < Ci::ApplicationController
+ before_action :authenticate_user!, except: [:build, :badge, :show]
+ before_action :authenticate_public_page!, only: :show
+ before_action :project, only: [:build, :show, :badge, :toggle_shared_runners, :dumped_yaml]
+ before_action :authorize_access_project!, except: [:build, :badge, :show, :new]
+ before_action :authorize_manage_project!, only: [: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]
+
+ def show
+ @ref = params[:ref]
+
+ @commits = @project.commits.reverse_order
+ if @ref
+ # unscope is required, because of default_scope defined in Ci::Build
+ builds = @project.builds.unscope(:select, :order).where(ref: @ref).select(:commit_id).distinct
+ @commits = @commits.where(id: builds)
+ end
+ @commits = @commits.page(params[:page]).per(20)
+ 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 namespace_project_runners_path(project.gl_project.namespace, project.gl_project)
+ 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
+ end
+end
diff --git a/app/controllers/ci/runner_projects_controller.rb b/app/controllers/ci/runner_projects_controller.rb
new file mode 100644
index 00000000000..97f01d40af5
--- /dev/null
+++ b/app/controllers/ci/runner_projects_controller.rb
@@ -0,0 +1,36 @@
+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)
+
+ path = runners_path(@project.gl_project)
+
+ if @runner.assign_to(project, current_user)
+ redirect_to path
+ else
+ redirect_to path, alert: 'Failed adding runner to project'
+ end
+ end
+
+ def destroy
+ runner_project = project.runner_projects.find(params[:id])
+ runner_project.destroy
+
+ redirect_to runners_path(@project.gl_project)
+ end
+
+ private
+
+ def project
+ @project ||= Ci::Project.find(params[:project_id])
+ end
+ end
+end
diff --git a/app/controllers/ci/services_controller.rb b/app/controllers/ci/services_controller.rb
new file mode 100644
index 00000000000..52c96a34ce8
--- /dev/null
+++ b/app/controllers/ci/services_controller.rb
@@ -0,0 +1,59 @@
+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
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index da96171e885..58e9049f158 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -1,10 +1,26 @@
class Dashboard::ProjectsController < Dashboard::ApplicationController
before_action :event_filter
+ def index
+ @projects = current_user.authorized_projects.sorted_by_activity.non_archived
+ @projects = @projects.includes(:namespace)
+ @last_push = current_user.recent_push
+
+ respond_to do |format|
+ format.html
+ format.atom do
+ event_filter
+ load_events
+ render layout: false
+ end
+ end
+ end
+
def starred
@projects = current_user.starred_projects
@projects = @projects.includes(:namespace, :forked_from_project, :tags)
@projects = @projects.sort(@sort = params[:sort])
+ @last_push = current_user.recent_push
@groups = []
respond_to do |format|
diff --git a/app/controllers/dashboard/snippets_controller.rb b/app/controllers/dashboard/snippets_controller.rb
new file mode 100644
index 00000000000..f4354c6d8ca
--- /dev/null
+++ b/app/controllers/dashboard/snippets_controller.rb
@@ -0,0 +1,10 @@
+class Dashboard::SnippetsController < Dashboard::ApplicationController
+ def index
+ @snippets = SnippetsFinder.new.execute(current_user,
+ filter: :by_user,
+ user: current_user,
+ scope: params[:scope]
+ )
+ @snippets = @snippets.page(params[:page]).per(PER_PAGE)
+ end
+end
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index d745131694b..4ebb3d7276e 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -1,23 +1,8 @@
class DashboardController < Dashboard::ApplicationController
- before_action :load_projects
before_action :event_filter, only: :activity
respond_to :html
- def show
- @projects = @projects.includes(:namespace)
- @last_push = current_user.recent_push
-
- respond_to do |format|
- format.html
- format.atom do
- event_filter
- load_events
- render layout: false
- end
- end
- end
-
def merge_requests
@merge_requests = get_merge_requests_collection
@merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE)
@@ -50,12 +35,15 @@ class DashboardController < Dashboard::ApplicationController
protected
- def load_projects
- @projects = current_user.authorized_projects.sorted_by_activity.non_archived
- end
-
def load_events
- @events = Event.in_projects(current_user.authorized_projects.pluck(:id))
+ project_ids =
+ if params[:filter] == "starred"
+ current_user.starred_projects
+ else
+ current_user.authorized_projects
+ end.pluck(:id)
+
+ @events = Event.in_projects(project_ids)
@events = @event_filter.apply_filter(@events).with_associations
@events = @events.limit(20).offset(params[:offset] || 0)
end
diff --git a/app/controllers/explore/application_controller.rb b/app/controllers/explore/application_controller.rb
index 4b275033d26..461fc059a3c 100644
--- a/app/controllers/explore/application_controller.rb
+++ b/app/controllers/explore/application_controller.rb
@@ -1,3 +1,5 @@
class Explore::ApplicationController < ApplicationController
+ skip_before_action :authenticate_user!, :reject_blocked
+
layout 'explore'
end
diff --git a/app/controllers/explore/groups_controller.rb b/app/controllers/explore/groups_controller.rb
index 55cda0cff17..9575a87ee41 100644
--- a/app/controllers/explore/groups_controller.rb
+++ b/app/controllers/explore/groups_controller.rb
@@ -1,7 +1,4 @@
class Explore::GroupsController < Explore::ApplicationController
- skip_before_action :authenticate_user!,
- :reject_blocked, :set_current_user_for_observers
-
def index
@groups = GroupsFinder.new.execute(current_user)
@groups = @groups.search(params[:search]) if params[:search].present?
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index 6c733c1ae4d..a5aeaed66c5 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -1,7 +1,4 @@
class Explore::ProjectsController < Explore::ApplicationController
- skip_before_action :authenticate_user!,
- :reject_blocked
-
def index
@projects = ProjectsFinder.new.execute(current_user)
@tags = @projects.tags_on(:tags)
diff --git a/app/controllers/explore/snippets_controller.rb b/app/controllers/explore/snippets_controller.rb
new file mode 100644
index 00000000000..b70ac51d06e
--- /dev/null
+++ b/app/controllers/explore/snippets_controller.rb
@@ -0,0 +1,6 @@
+class Explore::SnippetsController < Explore::ApplicationController
+ def index
+ @snippets = SnippetsFinder.new.execute(current_user, filter: :all)
+ @snippets = @snippets.page(params[:page]).per(PER_PAGE)
+ end
+end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 279c6ef0f4d..524218290c6 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -4,7 +4,7 @@ class GroupsController < Groups::ApplicationController
before_action :group, except: [:new, :create]
# Authorize
- before_action :authorize_read_group!, except: [:new, :create]
+ before_action :authorize_read_group!, except: [:show, :new, :create]
before_action :authorize_admin_group!, only: [:edit, :update, :destroy, :projects]
before_action :authorize_create_group!, only: [:new, :create]
@@ -14,6 +14,10 @@ class GroupsController < Groups::ApplicationController
layout :determine_layout
+ def index
+ redirect_to(current_user ? dashboard_groups_path : explore_groups_path)
+ end
+
def new
@group = Group.new
end
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index 71831c5380d..55050615473 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -1,7 +1,14 @@
class HelpController < ApplicationController
+ skip_before_action :authenticate_user!, :reject_blocked
+
layout 'help'
def index
+ @help_index = File.read(Rails.root.join('doc', 'README.md'))
+
+ # Prefix Markdown links with `help/` unless they already have been
+ # See http://rubular.com/r/nwwhzH6Z8X
+ @help_index.gsub!(/(\]\()(?!help\/)([^\)\(]+)(\))/, '\1help/\2\3')
end
def show
diff --git a/app/controllers/import/fogbugz_controller.rb b/app/controllers/import/fogbugz_controller.rb
new file mode 100644
index 00000000000..849646cd665
--- /dev/null
+++ b/app/controllers/import/fogbugz_controller.rb
@@ -0,0 +1,104 @@
+class Import::FogbugzController < Import::BaseController
+ before_action :verify_fogbugz_import_enabled
+ before_action :user_map, only: [:new_user_map, :create_user_map]
+
+ rescue_from Fogbugz::AuthenticationException, with: :fogbugz_unauthorized
+
+ def new
+
+ end
+
+ def callback
+ begin
+ res = Gitlab::FogbugzImport::Client.new(import_params.symbolize_keys)
+ rescue
+ # If the URI is invalid various errors can occur
+ return redirect_to new_import_fogbugz_path, alert: 'Could not connect to FogBugz, check your URL'
+ end
+ session[:fogbugz_token] = res.get_token
+ session[:fogbugz_uri] = params[:uri]
+
+ redirect_to new_user_map_import_fogbugz_path
+ end
+
+ def new_user_map
+
+ end
+
+ def create_user_map
+ user_map = params[:users]
+
+ unless user_map.is_a?(Hash) && user_map.all? { |k, v| !v[:name].blank? }
+ flash.now[:alert] = 'All users must have a name.'
+
+ render 'new_user_map' and return
+ end
+
+ session[:fogbugz_user_map] = user_map
+
+ flash[:notice] = 'The user map has been saved. Continue by selecting the projects you want to import.'
+
+ redirect_to status_import_fogbugz_path
+ end
+
+ def status
+ unless client.valid?
+ return redirect_to new_import_fogbugz_path
+ end
+
+ @repos = client.repos
+
+ @already_added_projects = current_user.created_projects.where(import_type: 'fogbugz')
+ already_added_projects_names = @already_added_projects.pluck(:import_source)
+
+ @repos.reject! { |repo| already_added_projects_names.include? repo.name }
+ end
+
+ def jobs
+ jobs = current_user.created_projects.where(import_type: 'fogbugz').to_json(only: [:id, :import_status])
+ render json: jobs
+ end
+
+ def create
+ @repo_id = params[:repo_id]
+ repo = client.repo(@repo_id)
+ fb_session = { uri: session[:fogbugz_uri], token: session[:fogbugz_token] }
+ @target_namespace = current_user.namespace
+ @project_name = repo.name
+
+ namespace = @target_namespace
+
+ umap = session[:fogbugz_user_map] || client.user_map
+
+ @project = Gitlab::FogbugzImport::ProjectCreator.new(repo, fb_session, namespace, current_user, umap).execute
+ end
+
+ private
+
+ def client
+ @client ||= Gitlab::FogbugzImport::Client.new(token: session[:fogbugz_token], uri: session[:fogbugz_uri])
+ end
+
+ def user_map
+ @user_map ||= begin
+ user_map = client.user_map
+
+ stored_user_map = session[:fogbugz_user_map]
+ user_map.update(stored_user_map) if stored_user_map
+
+ user_map
+ end
+ end
+
+ def fogbugz_unauthorized(exception)
+ redirect_to new_import_fogbugz_path, alert: exception.message
+ end
+
+ def import_params
+ params.permit(:uri, :email, :password)
+ end
+
+ def verify_fogbugz_import_enabled
+ not_found! unless fogbugz_import_enabled?
+ end
+end
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index eb3c8233530..8ef10a17f55 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -24,7 +24,7 @@ class InvitesController < ApplicationController
path =
if current_user
- dashboard_path
+ dashboard_projects_path
else
new_user_session_path
end
@@ -73,7 +73,7 @@ class InvitesController < ApplicationController
path = group_path(group)
else
label = "who knows what"
- path = dashboard_path
+ path = dashboard_projects_path
end
[label, path]
diff --git a/app/controllers/namespaces_controller.rb b/app/controllers/namespaces_controller.rb
index 83eec1bf4a2..282012c60a1 100644
--- a/app/controllers/namespaces_controller.rb
+++ b/app/controllers/namespaces_controller.rb
@@ -14,7 +14,7 @@ class NamespacesController < ApplicationController
if user
redirect_to user_path(user)
- elsif group && can?(current_user, :read_group, group)
+ elsif group
redirect_to group_path(group)
elsif current_user.nil?
authenticate_user!
diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb
index fc31118124b..dc22101cd5e 100644
--- a/app/controllers/oauth/applications_controller.rb
+++ b/app/controllers/oauth/applications_controller.rb
@@ -1,7 +1,7 @@
class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
include Gitlab::CurrentSettings
include PageLayoutHelper
-
+
before_action :verify_user_oauth_applications_enabled
before_action :authenticate_user!
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 523264b8ea9..f809fa7500a 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -71,7 +71,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
end
end
- rescue Gitlab::OAuth::SignupDisabledError => e
+ rescue Gitlab::OAuth::SignupDisabledError
label = Gitlab::OAuth::Provider.label_for(oauth['provider'])
message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed."
@@ -80,7 +80,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
flash[:notice] = message
-
+
redirect_to new_user_session_path
end
diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb
index 8450ba31021..2025158d065 100644
--- a/app/controllers/passwords_controller.rb
+++ b/app/controllers/passwords_controller.rb
@@ -1,41 +1,7 @@
class PasswordsController < Devise::PasswordsController
-
- def create
- email = resource_params[:email]
- resource_found = resource_class.find_by_email(email)
- if resource_found && resource_found.ldap_user?
- flash[:alert] = "Cannot reset password for LDAP user."
- respond_with({}, location: after_sending_reset_password_instructions_path_for(resource_name)) and return
- end
-
- self.resource = resource_class.send_reset_password_instructions(resource_params)
- if successfully_sent?(resource)
- respond_with({}, location: after_sending_reset_password_instructions_path_for(resource_name))
- else
- respond_with(resource)
- end
- end
-
- # After a user resets their password, prompt for 2FA code if enabled instead
- # of signing in automatically
- #
- # See http://git.io/vURrI
- def update
- super do |resource|
- # TODO (rspeicher): In Devise master (> 3.4.1), we can set
- # `Devise.sign_in_after_reset_password = false` and avoid this mess.
- if resource.errors.empty? && resource.try(:two_factor_enabled?)
- resource.unlock_access! if unlockable?(resource)
-
- # Since we are not signing this user in, we use the :updated_not_active
- # message which only contains "Your password was changed successfully."
- set_flash_message(:notice, :updated_not_active) if is_flashing_format?
-
- # Redirect to sign in so they can enter 2FA code
- respond_with(resource, location: new_session_path(resource)) and return
- end
- end
- end
+ before_action :resource_from_email, only: [:create]
+ before_action :prevent_ldap_reset, only: [:create]
+ before_action :throttle_reset, only: [:create]
def edit
super
@@ -56,4 +22,25 @@ class PasswordsController < Devise::PasswordsController
end
end
end
+
+ protected
+
+ def resource_from_email
+ email = resource_params[:email]
+ self.resource = resource_class.find_by_email(email)
+ end
+
+ def prevent_ldap_reset
+ return unless resource && resource.ldap_user?
+
+ redirect_to after_sending_reset_password_instructions_path_for(resource_name),
+ alert: "Cannot reset password for LDAP user."
+ end
+
+ def throttle_reset
+ return unless resource && resource.recently_sent_password_reset?
+
+ redirect_to new_password_path(resource_name),
+ alert: I18n.t('devise.passwords.recently_reset')
+ end
end
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
index f83b4abd1e2..a9a06ecc808 100644
--- a/app/controllers/profiles/preferences_controller.rb
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -31,6 +31,7 @@ class Profiles::PreferencesController < Profiles::ApplicationController
def preferences_params
params.require(:user).permit(
:color_scheme_id,
+ :layout,
:dashboard,
:project_view,
:theme_id
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index f9af0871cf1..e6b99be37fb 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -9,7 +9,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
end
def create
- if current_user.valid_otp?(params[:pin_code])
+ if current_user.validate_and_consume_otp!(params[:pin_code])
current_user.two_factor_enabled = true
@codes = current_user.generate_otp_backup_codes!
current_user.save!
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index ee88d49b400..519d6d6127e 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -25,4 +25,14 @@ class Projects::ApplicationController < ApplicationController
)
end
end
+
+ private
+
+ def ci_enabled
+ return render_404 unless @project.gitlab_ci?
+ end
+
+ def ci_project
+ @ci_project ||= @project.ensure_gitlab_ci_project
+ end
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 100d3d3b317..8776721d243 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -26,10 +26,16 @@ class Projects::BlobController < Projects::ApplicationController
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
- redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path))
+ respond_to do |format|
+ format.html { redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) }
+ format.json { render json: { message: "success", filePath: namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) } }
+ end
else
flash[:alert] = result[:message]
- render :new
+ respond_to do |format|
+ format.html { render :new }
+ format.json { render json: { message: "failed", filePath: namespace_project_new_blob_path(@project.namespace, @project, @id) } }
+ end
end
end
@@ -45,10 +51,16 @@ class Projects::BlobController < Projects::ApplicationController
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
- redirect_to after_edit_path
+ respond_to do |format|
+ format.html { redirect_to after_edit_path }
+ format.json { render json: { message: "success", filePath: after_edit_path } }
+ end
else
flash[:alert] = result[:message]
- render :edit
+ respond_to do |format|
+ format.html { render :edit }
+ format.json { render json: { message: "failed", filePath: namespace_project_new_blob_path(@project.namespace, @project, @id) } }
+ end
end
end
@@ -146,11 +158,19 @@ class Projects::BlobController < Projects::ApplicationController
@file_path =
if action_name.to_s == 'create'
+ if params[:file].present?
+ params[:file_name] = params[:file].original_filename
+ end
File.join(@path, File.basename(params[:file_name]))
else
@path
end
+ if params[:file].present?
+ params[:content] = Base64.encode64(params[:file].read)
+ params[:encoding] = 'base64'
+ end
+
@commit_params = {
file_path: @file_path,
current_branch: @current_branch,
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
new file mode 100644
index 00000000000..76c7f31f61b
--- /dev/null
+++ b/app/controllers/projects/builds_controller.rb
@@ -0,0 +1,25 @@
+class Projects::BuildsController < Projects::ApplicationController
+ before_action :ci_project
+ before_action :build
+
+ layout "project"
+
+ def show
+ @builds = @ci_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
+
+ private
+
+ def build
+ @build ||= ci_project.builds.unscoped.find_by!(id: params[:id])
+ end
+end
diff --git a/app/controllers/projects/ci_settings_controller.rb b/app/controllers/projects/ci_settings_controller.rb
new file mode 100644
index 00000000000..a263242a850
--- /dev/null
+++ b/app/controllers/projects/ci_settings_controller.rb
@@ -0,0 +1,36 @@
+class Projects::CiSettingsController < Projects::ApplicationController
+ before_action :ci_project
+ before_action :authorize_admin_project!
+
+ layout "project_settings"
+
+ def edit
+ end
+
+ def update
+ if ci_project.update_attributes(project_params)
+ Ci::EventService.new.change_project_settings(current_user, ci_project)
+
+ redirect_to edit_namespace_project_ci_settings_path(project.namespace, project), notice: 'Project was successfully updated.'
+ else
+ render action: "edit"
+ end
+ end
+
+ def destroy
+ ci_project.destroy
+ Ci::EventService.new.remove_project(current_user, ci_project)
+ project.gitlab_ci_service.update_attributes(active: false)
+
+ redirect_to project_path(project), notice: "CI was disabled for this project"
+ end
+
+ protected
+
+ 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
diff --git a/app/controllers/projects/ci_web_hooks_controller.rb b/app/controllers/projects/ci_web_hooks_controller.rb
new file mode 100644
index 00000000000..7f40ddcb3f3
--- /dev/null
+++ b/app/controllers/projects/ci_web_hooks_controller.rb
@@ -0,0 +1,45 @@
+class Projects::CiWebHooksController < Projects::ApplicationController
+ before_action :ci_project
+ before_action :authorize_admin_project!
+
+ layout "project_settings"
+
+ def index
+ @web_hooks = @ci_project.web_hooks
+ @web_hook = Ci::WebHook.new
+ end
+
+ def create
+ @web_hook = @ci_project.web_hooks.new(web_hook_params)
+ @web_hook.save
+
+ if @web_hook.valid?
+ redirect_to namespace_project_ci_web_hooks_path(@project.namespace, @project)
+ else
+ @web_hooks = @ci_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 namespace_project_ci_web_hooks_path(@project.namespace, @project)
+ end
+
+ private
+
+ def hook
+ @web_hook ||= @ci_project.web_hooks.find(params[:id])
+ end
+
+ def web_hook_params
+ params.require(:web_hook).permit(:url)
+ end
+end
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 78d42d695b6..1938c63c10c 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -22,6 +22,8 @@ class Projects::CommitController < Projects::ApplicationController
commit_id: @commit.id
}
+ @ci_commit = project.ci_commit(commit.sha)
+
respond_to do |format|
format.html
format.diff { render text: @commit.to_diff }
@@ -29,6 +31,13 @@ class Projects::CommitController < Projects::ApplicationController
end
end
+ def ci
+ @ci_commit = @project.ci_commit(@commit.sha)
+ @builds = @ci_commit.builds if @ci_commit
+ @notes_count = @commit.notes.count
+ @ci_project = @project.gitlab_ci_project
+ end
+
def branches
@branches = @project.repository.branch_names_contains(commit.id)
@tags = @project.repository.tag_names_contains(commit.id)
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index d9b3adae95b..d15004f93a6 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -16,10 +16,12 @@ class Projects::CompareController < Projects::ApplicationController
compare_result = CompareService.new.
execute(@project, head_ref, @project, base_ref)
- @commits = compare_result.commits
- @diffs = compare_result.diffs
- @commit = @commits.last
- @line_notes = []
+ if compare_result
+ @commits = compare_result.commits
+ @diffs = compare_result.diffs
+ @commit = @commits.last
+ @line_notes = []
+ end
end
def create
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index 9e72597ea87..8a785076bb7 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -13,10 +13,14 @@ class Projects::ForksController < Projects::ApplicationController
@forked_project = ::Projects::ForkService.new(project, current_user, namespace: namespace).execute
if @forked_project.saved? && @forked_project.forked?
- redirect_to(
- namespace_project_path(@forked_project.namespace, @forked_project),
- notice: 'Project was successfully forked.'
- )
+ if @forked_project.import_in_progress?
+ redirect_to namespace_project_import_path(@forked_project.namespace, @forked_project)
+ else
+ redirect_to(
+ namespace_project_path(@forked_project.namespace, @forked_project),
+ notice: 'Project was successfully forked.'
+ )
+ end
else
render :error
end
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
index 0b6f7f5c91e..418b92040bc 100644
--- a/app/controllers/projects/graphs_controller.rb
+++ b/app/controllers/projects/graphs_controller.rb
@@ -5,6 +5,7 @@ class Projects::GraphsController < Projects::ApplicationController
before_action :require_non_empty_project
before_action :assign_ref_vars
before_action :authorize_download_code!
+ before_action :ci_enabled, only: :ci
def show
respond_to do |format|
@@ -23,6 +24,16 @@ class Projects::GraphsController < Projects::ApplicationController
@commits_per_month = @commits_graph.commits_per_month
end
+ def ci
+ ci_project = @project.gitlab_ci_project
+
+ @charts = {}
+ @charts[:week] = Ci::Charts::WeekChart.new(ci_project)
+ @charts[:month] = Ci::Charts::MonthChart.new(ci_project)
+ @charts[:year] = Ci::Charts::YearChart.new(ci_project)
+ @charts[:build_times] = Ci::Charts::BuildTime.new(ci_project)
+ end
+
private
def fetch_graph
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index f3054881daf..7570934e727 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -7,6 +7,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits]
before_action :validates_merge_request, only: [:show, :diffs, :commits]
before_action :define_show_vars, only: [:show, :diffs, :commits]
+ before_action :ensure_ref_fetched, only: [:show, :commits, :diffs]
# Allow read any merge_request
before_action :authorize_read_merge_request!
@@ -149,6 +150,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
return access_denied! unless @merge_request.can_be_merged_by?(current_user)
if @merge_request.mergeable?
+ @merge_request.update(merge_error: nil)
MergeWorker.perform_async(@merge_request.id, current_user.id, params)
@status = true
else
@@ -257,8 +259,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@commits = @merge_request.commits
@merge_request_diff = @merge_request.merge_request_diff
- @source_branch = @merge_request.source_project.repository.find_branch(@merge_request.source_branch).try(:name)
-
+
if @merge_request.locked_long_ago?
@merge_request.unlock_mr
@merge_request.close
@@ -277,4 +278,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
:state_event, :description, :task_num, label_ids: []
)
end
+
+ # Make sure merge requests created before 8.0
+ # have head file in refs/merge-requests/
+ def ensure_ref_fetched
+ @merge_request.ensure_ref_fetched
+ end
end
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index 9efe9704d1e..86f4a02a6e9 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -66,12 +66,7 @@ class Projects::MilestonesController < Projects::ApplicationController
def destroy
return access_denied! unless can?(current_user, :admin_milestone, @project)
- update_params = { milestone: nil }
- @milestone.issues.each do |issue|
- Issues::UpdateService.new(@project, current_user, update_params).execute(issue)
- end
-
- @milestone.destroy
+ Milestones::DestroyService.new(project, current_user).execute(milestone)
respond_to do |format|
format.html { redirect_to namespace_project_milestones_path }
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index b82b6f45d59..cf73bc01c8f 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -78,7 +78,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@project.project_members.find_by(user_id: current_user).destroy
respond_to do |format|
- format.html { redirect_to dashboard_path }
+ format.html { redirect_to dashboard_projects_path }
format.js { render nothing: true }
end
end
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index 647c1454078..5f6fbce795e 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -17,8 +17,7 @@ class Projects::RawController < Projects::ApplicationController
send_data(
@blob.data,
type: type,
- disposition: 'inline',
- filename: @blob.name
+ disposition: 'inline'
)
else
not_found!
@@ -30,6 +29,8 @@ class Projects::RawController < Projects::ApplicationController
def get_blob_type
if @blob.text?
'text/plain; charset=utf-8'
+ elsif @blob.image?
+ @blob.content_type
else
'application/octet-stream'
end
diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb
new file mode 100644
index 00000000000..6cb6e3ef6d4
--- /dev/null
+++ b/app/controllers/projects/runners_controller.rb
@@ -0,0 +1,65 @@
+class Projects::RunnersController < Projects::ApplicationController
+ before_action :ci_project
+ before_action :set_runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
+ before_action :authorize_admin_project!
+
+ layout 'project_settings'
+
+ def index
+ @runners = @ci_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 runner_path(@runner), notice: 'Runner was successfully updated.'
+ else
+ redirect_to runner_path(@runner), alert: 'Runner was not updated.'
+ end
+ end
+
+ def destroy
+ if @runner.only_for?(@ci_project)
+ @runner.destroy
+ end
+
+ redirect_to runners_path(@project)
+ end
+
+ def resume
+ if @runner.update_attributes(active: true)
+ redirect_to runner_path(@runner), notice: 'Runner was successfully updated.'
+ else
+ redirect_to runner_path(@runner), alert: 'Runner was not updated.'
+ end
+ end
+
+ def pause
+ if @runner.update_attributes(active: false)
+ redirect_to runner_path(@runner), notice: 'Runner was successfully updated.'
+ else
+ redirect_to runner_path(@runner), alert: 'Runner was not updated.'
+ end
+ end
+
+ def show
+ end
+
+ protected
+
+ def set_runner
+ @runner ||= @ci_project.runners.find(params[:id])
+ end
+
+ def runner_params
+ params.require(:runner).permit(:description, :tag_list, :contacted_at, :active)
+ end
+end
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index b0cf5866d41..3047ee8a1ff 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -2,7 +2,7 @@ class Projects::ServicesController < Projects::ApplicationController
ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_version, :subdomain,
:room, :recipients, :project_url, :webhook,
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
- :build_key, :server, :teamcity_url, :build_type,
+ :build_key, :server, :teamcity_url, :drone_url, :build_type,
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
:colorize_messages, :channels,
:push_events, :issues_events, :merge_requests_events, :tag_push_events,
@@ -58,6 +58,8 @@ class Projects::ServicesController < Projects::ApplicationController
end
def service_params
- params.require(:service).permit(ALLOWED_PARAMS)
+ service_params = params.require(:service).permit(ALLOWED_PARAMS)
+ service_params.delete("password") if service_params["password"].blank?
+ service_params
end
end
diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb
new file mode 100644
index 00000000000..782ebd01b05
--- /dev/null
+++ b/app/controllers/projects/triggers_controller.rb
@@ -0,0 +1,35 @@
+class Projects::TriggersController < Projects::ApplicationController
+ before_action :ci_project
+ before_action :authorize_admin_project!
+
+ layout 'project_settings'
+
+ def index
+ @triggers = @ci_project.triggers
+ @trigger = Ci::Trigger.new
+ end
+
+ def create
+ @trigger = @ci_project.triggers.new
+ @trigger.save
+
+ if @trigger.valid?
+ redirect_to namespace_project_triggers_path(@project.namespace, @project)
+ else
+ @triggers = @ci_project.triggers.select(&:persisted?)
+ render :index
+ end
+ end
+
+ def destroy
+ trigger.destroy
+
+ redirect_to namespace_project_triggers_path(@project.namespace, @project)
+ end
+
+ private
+
+ def trigger
+ @trigger ||= @ci_project.triggers.find(params[:id])
+ end
+end
diff --git a/app/controllers/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb
new file mode 100644
index 00000000000..d6561a45a70
--- /dev/null
+++ b/app/controllers/projects/variables_controller.rb
@@ -0,0 +1,25 @@
+class Projects::VariablesController < Projects::ApplicationController
+ before_action :ci_project
+ before_action :authorize_admin_project!
+
+ layout 'project_settings'
+
+ def show
+ end
+
+ def update
+ if ci_project.update_attributes(project_params)
+ Ci::EventService.new.change_project_settings(current_user, ci_project)
+
+ redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variables were successfully updated.'
+ else
+ render action: 'show'
+ end
+ end
+
+ private
+
+ def project_params
+ params.require(:project).permit({ variables_attributes: [:id, :key, :value, :_destroy] })
+ end
+end
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 50512cb6dc3..88fccfed509 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -5,7 +5,6 @@ class Projects::WikisController < Projects::ApplicationController
before_action :authorize_create_wiki!, only: [:edit, :create, :history]
before_action :authorize_admin_wiki!, only: :destroy
before_action :load_project_wiki
- include WikiHelper
def pages
@wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page]).per(PER_PAGE)
@@ -99,7 +98,7 @@ class Projects::WikisController < Projects::ApplicationController
# Call #wiki to make sure the Wiki Repo is initialized
@project_wiki.wiki
- rescue ProjectWiki::CouldNotCreateWikiError => ex
+ rescue ProjectWiki::CouldNotCreateWikiError
flash[:notice] = "Could not create Wiki Repository at this time. Please try again later."
redirect_to project_path(@project)
return false
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index dafc11d0707..213c2a7173b 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -10,6 +10,10 @@ class ProjectsController < ApplicationController
layout :determine_layout
+ def index
+ redirect_to(current_user ? root_path : explore_root_path)
+ end
+
def new
@project = Project.new
end
@@ -82,6 +86,10 @@ class ProjectsController < ApplicationController
if @project.empty_repo?
render 'projects/empty'
else
+ if current_user
+ @membership = @project.project_member_by_id(current_user.id)
+ end
+
render :show
end
else
@@ -105,7 +113,7 @@ class ProjectsController < ApplicationController
if request.referer.include?('/admin')
redirect_to admin_namespaces_projects_path
else
- redirect_to dashboard_path
+ redirect_to dashboard_projects_path
end
rescue Projects::DestroyService::DestroyError => ex
redirect_to edit_project_path(@project), alert: ex.message
diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb
index fdfe00dc135..ad04c646e1b 100644
--- a/app/controllers/root_controller.rb
+++ b/app/controllers/root_controller.rb
@@ -6,10 +6,10 @@
#
# For users who haven't customized the setting, we simply delegate to
# `DashboardController#show`, which is the default.
-class RootController < DashboardController
- before_action :redirect_to_custom_dashboard, only: [:show]
+class RootController < Dashboard::ProjectsController
+ before_action :redirect_to_custom_dashboard, only: [:index]
- def show
+ def index
super
end
@@ -20,7 +20,12 @@ class RootController < DashboardController
case current_user.dashboard
when 'stars'
+ flash.keep
redirect_to starred_dashboard_projects_path
+ when 'project_activity'
+ redirect_to activity_dashboard_path
+ when 'starred_project_activity'
+ redirect_to activity_dashboard_path(filter: 'starred')
else
return
end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 8389f07a3bd..1b60d3e27d0 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -8,6 +8,8 @@ class SessionsController < Devise::SessionsController
def new
if Gitlab.config.ldap.enabled
@ldap_servers = Gitlab::LDAP::Config.servers
+ else
+ @ldap_servers = []
end
super
@@ -97,7 +99,7 @@ class SessionsController < Devise::SessionsController
end
def valid_otp_attempt?(user)
- user.valid_otp?(user_params[:otp_attempt]) ||
+ user.validate_and_consume_otp!(user_params[:otp_attempt]) ||
user.invalidate_otp_backup_code!(user_params[:otp_attempt])
end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 8e7e45c781f..9f9f9a92f11 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -24,13 +24,9 @@ class SnippetsController < ApplicationController
scope: params[:scope] }).
page(params[:page]).per(PER_PAGE)
- if @user == current_user
- render 'current_user_index'
- else
- render 'user_index'
- end
+ render 'index'
else
- @snippets = SnippetsFinder.new.execute(current_user, filter: :all).page(params[:page]).per(PER_PAGE)
+ redirect_to(current_user ? dashboard_snippets_path : explore_snippets_path)
end
end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index ab89aa2c53a..6aa16673d63 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -39,7 +39,7 @@ class IssuableFinder
items = by_assignee(items)
items = by_author(items)
items = by_label(items)
- items = sort(items)
+ sort(items)
end
def group
diff --git a/app/finders/trending_projects_finder.rb b/app/finders/trending_projects_finder.rb
index f3f4d461efa..9ea342cb26d 100644
--- a/app/finders/trending_projects_finder.rb
+++ b/app/finders/trending_projects_finder.rb
@@ -2,21 +2,12 @@ class TrendingProjectsFinder
def execute(current_user, start_date = nil)
start_date ||= Date.today - 1.month
+ projects = projects_for(current_user)
+
# Determine trending projects based on comments count
# for period of time - ex. month
- trending_project_ids = Note.
- select("notes.project_id, count(notes.project_id) as pcount").
- where('notes.created_at > ?', start_date).
- group("project_id").
- reorder("pcount DESC").
- map(&:project_id)
-
- sql_order_ids = trending_project_ids.reverse.
- map { |project_id| "id = #{project_id}" }.join(", ")
-
- # Get list of projects that user allowed to see
- projects = projects_for(current_user)
- projects.where(id: trending_project_ids).reorder(sql_order_ids)
+ projects.joins(:notes).where('notes.created_at > ?', start_date).
+ group("projects.id").reorder("count(notes.id) DESC")
end
private
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index a803b66c502..cab2278adb7 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -13,7 +13,9 @@ module ApplicationHelper
# current_controller?(:commits) # => false
# current_controller?(:commits, :tree) # => true
def current_controller?(*args)
- args.any? { |v| v.to_s.downcase == controller.controller_name }
+ args.any? do |v|
+ v.to_s.downcase == controller.controller_name || v.to_s.downcase == controller.controller_path
+ end
end
# Check if a particular action is the current one
@@ -33,7 +35,7 @@ module ApplicationHelper
def project_icon(project_id, options = {})
project =
if project_id.is_a?(Project)
- project = project_id
+ project_id
else
Project.find_with_namespace(project_id)
end
@@ -82,7 +84,7 @@ module ApplicationHelper
end
def default_avatar
- image_path('no_avatar.png')
+ 'no_avatar.png'
end
def last_commit(project)
@@ -201,7 +203,7 @@ module ApplicationHelper
class: "#{html_class} js-timeago",
datetime: time.getutc.iso8601,
title: time.in_time_zone.stamp('Aug 21, 2011 9:23pm'),
- data: { toggle: 'tooltip', placement: placement }
+ data: { toggle: 'tooltip', placement: placement, container: 'body' }
element += javascript_tag "$('.js-timeago').timeago()" unless skip_js
@@ -312,4 +314,8 @@ module ApplicationHelper
html.html_safe
end
+
+ def truncate_first_line(message, length = 50)
+ truncate(message.each_line.first.chomp, length: length) if message
+ end
end
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index 0e7a37b4cc6..cd99a232403 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -1,6 +1,6 @@
module AuthHelper
PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2).freeze
- FORM_BASED_PROVIDERS = [/\Aldap/, 'kerberos'].freeze
+ FORM_BASED_PROVIDERS = [/\Aldap/, 'crowd'].freeze
def ldap_enabled?
Gitlab.config.ldap.enabled
@@ -26,6 +26,10 @@ module AuthHelper
auth_providers.select { |provider| form_based_provider?(provider) }
end
+ def crowd_enabled?
+ auth_providers.include? :crowd
+ end
+
def button_based_providers
auth_providers.reject { |provider| form_based_provider?(provider) }
end
@@ -36,7 +40,7 @@ module AuthHelper
if provider_has_icon?(provider)
file_name = "#{provider.to_s.split('_').first}_#{size}.png"
- image_tag(image_path("auth_buttons/#{file_name}"), alt: label, title: "Sign in with #{label}")
+ image_tag("auth_buttons/#{file_name}", alt: label, title: "Sign in with #{label}")
else
label
end
diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb
new file mode 100644
index 00000000000..1b5a2c31d74
--- /dev/null
+++ b/app/helpers/builds_helper.rb
@@ -0,0 +1,13 @@
+module BuildsHelper
+ def build_ref_link build
+ gitlab_ref_link build.project, build.ref
+ end
+
+ def build_commit_link build
+ gitlab_commit_link build.project, build.short_sha
+ end
+
+ def build_url(build)
+ namespace_project_build_path(build.gl_project, build.project, build)
+ end
+end
diff --git a/app/helpers/ci/gitlab_helper.rb b/app/helpers/ci/gitlab_helper.rb
new file mode 100644
index 00000000000..baddbc806f2
--- /dev/null
+++ b/app/helpers/ci/gitlab_helper.rb
@@ -0,0 +1,36 @@
+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.ci_yaml_file
+ "#{project.gitlab_url}/edit/master/.gitlab-ci.yml"
+ else
+ "#{project.gitlab_url}/new/master"
+ end
+ end
+ end
+end
diff --git a/app/helpers/ci/projects_helper.rb b/app/helpers/ci/projects_helper.rb
new file mode 100644
index 00000000000..fd991a4165a
--- /dev/null
+++ b/app/helpers/ci/projects_helper.rb
@@ -0,0 +1,36 @@
+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
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
new file mode 100644
index 00000000000..dbd1e26fa79
--- /dev/null
+++ b/app/helpers/ci_status_helper.rb
@@ -0,0 +1,45 @@
+module CiStatusHelper
+ def ci_status_path(ci_commit)
+ project = ci_commit.gl_project
+ ci_namespace_project_commit_path(project.namespace, project, ci_commit.sha)
+ end
+
+ def ci_status_icon(ci_commit)
+ ci_icon_for_status(ci_commit.status)
+ end
+
+ def ci_status_color(ci_commit)
+ case ci_commit.status
+ when 'success'
+ 'green'
+ when 'failed'
+ 'red'
+ when 'running', 'pending'
+ 'yellow'
+ else
+ 'gray'
+ end
+ end
+
+ def ci_status_with_icon(status)
+ content_tag :span, class: "ci-status ci-#{status}" do
+ ci_icon_for_status(status) + '&nbsp;'.html_safe + status
+ end
+ end
+
+ def ci_icon_for_status(status)
+ icon_name =
+ case status
+ when 'success'
+ 'check'
+ when 'failed'
+ 'close'
+ when 'running', 'pending'
+ 'clock-o'
+ else
+ 'circle'
+ end
+
+ icon(icon_name)
+ end
+end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index d13d80be293..9df20c9fce5 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -135,7 +135,7 @@ module CommitsHelper
# size: size of the avatar image in px
def commit_person_link(commit, options = {})
user = commit.send(options[:source])
-
+
source_name = clean(commit.send "#{options[:source]}_name".to_sym)
source_email = clean(commit.send "#{options[:source]}_email".to_sym)
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 1bd3ec5e0e0..b896fba3704 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -137,7 +137,7 @@ module DiffHelper
# Always use HTML to handle case where JSON diff rendered this button
params_copy.delete(:format)
- link_to url_for(params_copy), id: "commit-diff-viewtype", class: (params[:view] != 'parallel' ? 'btn btn-sm active' : 'btn btn-sm') do
+ link_to url_for(params_copy), id: "inline-diff-btn", class: (params[:view] != 'parallel' ? 'btn btn-sm active' : 'btn btn-sm') do
'Inline'
end
end
@@ -148,7 +148,7 @@ module DiffHelper
# Always use HTML to handle case where JSON diff rendered this button
params_copy.delete(:format)
- link_to url_for(params_copy), id: "commit-diff-viewtype", class: (params[:view] == 'parallel' ? 'btn active btn-sm' : 'btn btn-sm') do
+ link_to url_for(params_copy), id: "parallel-diff-btn", class: (params[:view] == 'parallel' ? 'btn active btn-sm' : 'btn btn-sm') do
'Side-by-side'
end
end
@@ -167,4 +167,23 @@ module DiffHelper
content_tag(:span, commit_id, class: 'monospace'),
].join(' ').html_safe
end
+
+ def commit_for_diff(diff)
+ if diff.deleted_file
+ @merge_request ? @merge_request.commits.last : @commit.parents.first
+ else
+ @commit
+ end
+ end
+
+ def diff_file_html_data(project, diff_commit, diff_file)
+ {
+ blob_diff_path: namespace_project_blob_diff_path(project.namespace, project,
+ tree_join(diff_commit.id, diff_file.file_path))
+ }
+ end
+
+ def editable_diff?(diff)
+ !diff.deleted_file && @merge_request && @merge_request.source_project
+ end
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 8428281f8f6..6f69c2a9f32 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -27,16 +27,13 @@ module EventsHelper
key = key.to_s
active = 'active' if @event_filter.active?(key)
link_opts = {
- class: 'event_filter_link',
+ class: "event-filter-link btn btn-default #{active}",
id: "#{key}_event_filter",
title: "Filter by #{tooltip.downcase}",
- data: { toggle: 'tooltip', placement: 'top' }
}
- content_tag :li, class: "filter_icon #{active}" do
- link_to request.path, link_opts do
- icon(icon_for_event[key]) + content_tag(:span, ' ' + tooltip)
- end
+ link_to request.path, link_opts do
+ content_tag(:span, ' ' + tooltip)
end
end
@@ -49,6 +46,14 @@ module EventsHelper
}
end
+ def event_preposition(event)
+ if event.push? || event.commented? || event.target
+ "at"
+ elsif event.milestone?
+ "in"
+ end
+ end
+
def event_feed_title(event)
words = []
words << event.author_name
@@ -65,8 +70,11 @@ module EventsHelper
words << "##{truncate event.note_target_iid}"
end
words << "at"
+ elsif event.milestone?
+ words << "##{event.target_iid}" if event.target_iid
+ words << "in"
elsif event.target
- words << "##{event.target_iid}:"
+ words << "##{event.target_iid}:"
words << event.target.title if event.target.respond_to?(:title)
words << "at"
end
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 0d175e1ea18..40161c5a641 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -21,7 +21,7 @@ module GitlabMarkdownHelper
gfm_body = Gitlab::Markdown.gfm(escaped_body, project: @project, current_user: current_user)
- fragment = Nokogiri::XML::DocumentFragment.parse(gfm_body)
+ fragment = Nokogiri::HTML::DocumentFragment.parse(gfm_body)
if fragment.children.size == 1 && fragment.children[0].name == 'a'
# Fragment has only one node, and it's a link generated by `gfm`.
# Replace it with our requested link.
@@ -171,7 +171,7 @@ module GitlabMarkdownHelper
# and return true. Otherwise return false.
def truncate_if_block(node, truncated)
if node.element? && node.description.block? && !truncated
- node.content = "#{node.content}..." if node.next_sibling
+ node.inner_html = "#{node.inner_html}..." if node.next_sibling
true
else
truncated
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index d0fae255a04..4d9da6ff837 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -17,6 +17,14 @@ module GitlabRoutingHelper
namespace_project_path(project.namespace, project, *args)
end
+ def project_files_path(project, *args)
+ namespace_project_tree_path(project.namespace, project, @ref || project.repository.root_ref)
+ end
+
+ def project_commits_path(project, *args)
+ namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref)
+ end
+
def activity_project_path(project, *args)
activity_namespace_project_path(project.namespace, project, *args)
end
@@ -25,6 +33,14 @@ module GitlabRoutingHelper
edit_namespace_project_path(project.namespace, project, *args)
end
+ def runners_path(project, *args)
+ namespace_project_runners_path(project.namespace, project, *args)
+ end
+
+ def runner_path(runner, *args)
+ namespace_project_runner_path(@project.namespace, @project, runner, *args)
+ end
+
def issue_path(entity, *args)
namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args)
end
diff --git a/app/helpers/graph_helper.rb b/app/helpers/graph_helper.rb
index e1dda20de85..1e372d5631d 100644
--- a/app/helpers/graph_helper.rb
+++ b/app/helpers/graph_helper.rb
@@ -1,7 +1,10 @@
module GraphHelper
def get_refs(repo, commit)
refs = ""
- refs << commit.ref_names(repo).join(' ')
+ # Commit::ref_names already strips the refs/XXX from important refs (e.g. refs/heads/XXX)
+ # so anything leftover is internally used by GitLab
+ commit_refs = commit.ref_names(repo).reject{ |name| name.starts_with?('refs/') }
+ refs << commit_refs.join(' ')
# append note count
refs << "[#{@graph.notes[commit.id]}]" if @graph.notes[commit.id] > 0
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index b067cb54a43..1d36969cd62 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -27,7 +27,16 @@ module GroupsHelper
if group && group.avatar.present?
group.avatar.url
else
- image_path('no_group_avatar.png')
+ 'no_group_avatar.png'
+ end
+ end
+
+ def group_title(group, name = nil, url = nil)
+ full_title = link_to(simple_sanitize(group.name), group_path(group))
+ full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url) if name
+
+ content_tag :span do
+ full_title
end
end
end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index f8169b4f288..81773e7afcf 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -71,4 +71,17 @@ module MergeRequestsHelper
merge_request.source_branch
end
end
+
+ def format_mr_branch_names(merge_request)
+ source_path = merge_request.source_project_path
+ target_path = merge_request.target_project_path
+ source_branch = merge_request.source_branch
+ target_branch = merge_request.target_branch
+
+ if source_path == target_path
+ [source_branch, target_branch]
+ else
+ ["#{source_path}:#{source_branch}", "#{target_path}:#{target_branch}"]
+ end
+ end
end
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index 2f8e64c375f..cf11f8e5320 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -12,4 +12,49 @@ module NotificationsHelper
icon('circle-o', class: 'ns-default')
end
end
+
+ def notification_list_item(notification_level, user_membership)
+ case notification_level
+ when Notification::N_DISABLED
+ content_tag(:li, class: active_level_for(user_membership, Notification::N_DISABLED)) do
+ link_to '#', class: 'update-notification', data: { notification_level: Notification::N_DISABLED } do
+ icon('microphone-slash fw', text: 'Disabled')
+ end
+ end
+ when Notification::N_PARTICIPATING
+ content_tag(:li, class: active_level_for(user_membership, Notification::N_PARTICIPATING)) do
+ link_to '#', class: 'update-notification', data: { notification_level: Notification::N_PARTICIPATING } do
+ icon('volume-up fw', text: 'Participate')
+ end
+ end
+ when Notification::N_WATCH
+ content_tag(:li, class: active_level_for(user_membership, Notification::N_WATCH)) do
+ link_to '#', class: 'update-notification', data: { notification_level: Notification::N_WATCH } do
+ icon('eye fw', text: 'Watch')
+ end
+ end
+ when Notification::N_MENTION
+ content_tag(:li, class: active_level_for(user_membership, Notification::N_MENTION)) do
+ link_to '#', class: 'update-notification', data: { notification_level: Notification::N_MENTION } do
+ icon('at fw', text: 'On mention')
+ end
+ end
+ when Notification::N_GLOBAL
+ content_tag(:li, class: active_level_for(user_membership, Notification::N_GLOBAL)) do
+ link_to '#', class: 'update-notification', data: { notification_level: Notification::N_GLOBAL } do
+ icon('globe fw', text: 'Global')
+ end
+ end
+ else
+ # do nothing
+ end
+ end
+
+ def notification_label(user_membership)
+ Notification.new(user_membership).to_s
+ end
+
+ def active_level_for(user_membership, level)
+ 'active' if user_membership.notification_level == level
+ end
end
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 8473d6d75d0..775cf5a3dd4 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -26,9 +26,31 @@ module PageLayoutHelper
def fluid_layout(enabled = false)
if @fluid_layout.nil?
- @fluid_layout = enabled
+ @fluid_layout = (current_user && current_user.layout == "fluid") || enabled
else
@fluid_layout
end
end
+
+ def blank_container(enabled = false)
+ if @blank_container.nil?
+ @blank_container = enabled
+ else
+ @blank_container
+ end
+ end
+
+ def container_class
+ css_class = "container-fluid"
+
+ unless fluid_layout
+ css_class += " container-limited"
+ end
+
+ if blank_container
+ css_class += " container-blank"
+ end
+
+ css_class
+ end
end
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index 7f1b6a69926..4710171ebaa 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -1,9 +1,18 @@
# Helper methods for per-User preferences
module PreferencesHelper
+ def layout_choices
+ [
+ ['Fixed', :fixed],
+ ['Fluid', :fluid]
+ ]
+ end
+
# Maps `dashboard` values to more user-friendly option text
DASHBOARD_CHOICES = {
projects: 'Your Projects (default)',
- stars: 'Starred Projects'
+ stars: 'Starred Projects',
+ project_activity: "Your Projects' Activity",
+ starred_project_activity: "Starred Projects' Activity"
}.with_indifferent_access.freeze
# Returns an Array usable by a select field for more user-friendly option text
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index ab9b068de05..a0220af4c30 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -43,24 +43,22 @@ module ProjectsHelper
end
end
- def project_title(project)
- if project.group
- content_tag :span do
- link_to(
- simple_sanitize(project.group.name), group_path(project.group)
- ) + ' / ' +
- link_to(simple_sanitize(project.name),
- project_path(project))
- end
- else
- owner = project.namespace.owner
- content_tag :span do
- link_to(
- simple_sanitize(owner.name), user_path(owner)
- ) + ' / ' +
- link_to(simple_sanitize(project.name),
- project_path(project))
+ def project_title(project, name = nil, url = nil)
+ namespace_link =
+ if project.group
+ link_to(simple_sanitize(project.group.name), group_path(project.group))
+ else
+ owner = project.namespace.owner
+ link_to(simple_sanitize(owner.name), user_path(owner))
end
+
+ project_link = link_to(simple_sanitize(project.name), project_path(project))
+
+ full_title = namespace_link + ' / ' + project_link
+ full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url) if name
+
+ content_tag :span do
+ full_title
end
end
@@ -158,8 +156,8 @@ module ProjectsHelper
end
end
- def repository_size(project = nil)
- "#{(project || @project).repository_size} MB"
+ def repository_size(project = @project)
+ "#{project.repository_size} MB"
rescue
# In order to prevent 500 error
# when application cannot allocate memory
@@ -298,7 +296,7 @@ module ProjectsHelper
def readme_cache_key
sha = @project.commit.try(:sha) || 'nil'
- [@project.id, sha, "readme"].join('-')
+ [@project.path_with_namespace, sha, "readme"].join('-')
end
def round_commit_count(project)
@@ -315,6 +313,10 @@ module ProjectsHelper
end
end
+ def current_ref
+ @ref || @repository.try(:root_ref)
+ end
+
private
def filename_path(project, filename)
diff --git a/app/helpers/runners_helper.rb b/app/helpers/runners_helper.rb
new file mode 100644
index 00000000000..5d7d06c8490
--- /dev/null
+++ b/app/helpers/runners_helper.rb
@@ -0,0 +1,20 @@
+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
diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb
new file mode 100644
index 00000000000..8142f733e76
--- /dev/null
+++ b/app/helpers/time_helper.rb
@@ -0,0 +1,27 @@
+module TimeHelper
+ 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
+
+
+ def date_from_to(from, to)
+ "#{from.to_s(:short)} - #{to.to_s(:short)}"
+ end
+end
diff --git a/app/helpers/triggers_helper.rb b/app/helpers/triggers_helper.rb
new file mode 100644
index 00000000000..2a3a7e80fca
--- /dev/null
+++ b/app/helpers/triggers_helper.rb
@@ -0,0 +1,5 @@
+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
diff --git a/app/helpers/version_check_helper.rb b/app/helpers/version_check_helper.rb
index f64d730b448..a674564c4ec 100644
--- a/app/helpers/version_check_helper.rb
+++ b/app/helpers/version_check_helper.rb
@@ -1,6 +1,6 @@
module VersionCheckHelper
def version_status_badge
- if Rails.env.production?
+ if Rails.env.production? && current_application_settings.version_check_enabled
image_tag VersionCheck.new.url
end
end
diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb
deleted file mode 100644
index f8a96516e61..00000000000
--- a/app/helpers/wiki_helper.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-module WikiHelper
- # Rails v4.1.9+ escapes all model IDs, converting slashes into %2F. The
- # only way around this is to implement our own path generators.
- def namespace_project_wiki_path(namespace, project, wiki_page, *args)
- slug =
- case wiki_page
- when Symbol
- wiki_page
- when String
- wiki_page
- else
- wiki_page.slug
- end
- namespace_project_path(namespace, project) + "/wikis/#{slug}"
- end
-
- def edit_namespace_project_wiki_path(namespace, project, wiki_page, *args)
- namespace_project_wiki_path(namespace, project, wiki_page) + '/edit'
- end
-
- def history_namespace_project_wiki_path(namespace, project, wiki_page, *args)
- namespace_project_wiki_path(namespace, project, wiki_page) + '/history'
- end
-end
diff --git a/app/mailers/ci/emails/builds.rb b/app/mailers/ci/emails/builds.rb
new file mode 100644
index 00000000000..6fb4fba85e5
--- /dev/null
+++ b/app/mailers/ci/emails/builds.rb
@@ -0,0 +1,17 @@
+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
diff --git a/app/mailers/ci/notify.rb b/app/mailers/ci/notify.rb
new file mode 100644
index 00000000000..404842cf213
--- /dev/null
+++ b/app/mailers/ci/notify.rb
@@ -0,0 +1,46 @@
+module Ci
+ class Notify < ActionMailer::Base
+ include Ci::Emails::Builds
+
+ 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
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index 63d4aca61af..87ba94a583d 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -12,7 +12,7 @@ module Emails
to: recipient(recipient_id),
subject: subject("#{@commit.title} (#{@commit.short_id})"))
- SentNotification.record(@commit, recipient_id, reply_key)
+ SentNotification.record_note(@note, recipient_id, reply_key)
end
def note_issue_email(recipient_id, note_id)
@@ -27,7 +27,7 @@ module Emails
to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})"))
- SentNotification.record(@issue, recipient_id, reply_key)
+ SentNotification.record_note(@note, recipient_id, reply_key)
end
def note_merge_request_email(recipient_id, note_id)
@@ -43,7 +43,7 @@ module Emails
to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
- SentNotification.record(@merge_request, recipient_id, reply_key)
+ SentNotification.record_note(@note, recipient_id, reply_key)
end
end
end
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index 4a6e18e6a74..caba63006da 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -50,10 +50,11 @@ module Emails
subject: subject("Invitation declined"))
end
- def project_was_moved_email(project_id, user_id)
+ def project_was_moved_email(project_id, user_id, old_path_with_namespace)
@current_user = @user = User.find user_id
@project = Project.find project_id
@target_url = namespace_project_url(@project.namespace, @project)
+ @old_path_with_namespace = old_path_with_namespace
mail(to: @user.notification_email,
subject: subject("Project was moved"))
end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 5717c89e61d..50a409c3754 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -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
@@ -110,7 +110,7 @@ class Notify < BaseMailer
if reply_key
headers['X-GitLab-Reply-Key'] = reply_key
- address = Mail::Address.new(Gitlab::ReplyByEmail.reply_address(reply_key))
+ address = Mail::Address.new(Gitlab::IncomingEmail.reply_address(reply_key))
address.display_name = @project.name_with_namespace
headers['Reply-To'] = address
@@ -140,7 +140,7 @@ class Notify < BaseMailer
# * have a 'In-Reply-To' or 'References' header that references the original 'Message-ID'
#
def mail_answer_thread(model, headers = {})
- headers['Message-ID'] = SecureRandom.hex
+ headers['Message-ID'] = "<#{SecureRandom.hex}@#{Gitlab.config.gitlab.host}>"
headers['In-Reply-To'] = message_id(model)
headers['References'] = message_id(model)
@@ -150,6 +150,6 @@ class Notify < BaseMailer
end
def reply_key
- @reply_key ||= Gitlab::ReplyByEmail.reply_key
+ @reply_key ||= SentNotification.reply_key
end
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index f8e5afa9b01..a020b24a550 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -149,6 +149,7 @@ class Ability
:admin_merge_request,
:create_merge_request,
:create_wiki,
+ :manage_builds,
:push_code
]
end
diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb
index c8c39db11bc..89b3116b9f2 100644
--- a/app/models/abuse_report.rb
+++ b/app/models/abuse_report.rb
@@ -1,9 +1,21 @@
+# == Schema Information
+#
+# Table name: abuse_reports
+#
+# id :integer not null, primary key
+# reporter_id :integer
+# user_id :integer
+# message :text
+# created_at :datetime
+# updated_at :datetime
+#
+
class AbuseReport < ActiveRecord::Base
- belongs_to :reporter, class_name: "User"
+ belongs_to :reporter, class_name: 'User'
belongs_to :user
validates :reporter, presence: true
validates :user, presence: true
validates :message, presence: true
- validates :user_id, uniqueness: { scope: :reporter_id }
+ validates :user_id, uniqueness: true
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 8f27e35d723..c8841178e93 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -83,7 +83,7 @@ class ApplicationSetting < ActiveRecord::Base
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
restricted_signup_domains: Settings.gitlab['restricted_signup_domains'],
- import_sources: ['github','bitbucket','gitlab','gitorious','google_code','git']
+ import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git']
)
end
diff --git a/app/models/ci/application_setting.rb b/app/models/ci/application_setting.rb
new file mode 100644
index 00000000000..0cf496f7d81
--- /dev/null
+++ b/app/models/ci/application_setting.rb
@@ -0,0 +1,27 @@
+# == 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
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
new file mode 100644
index 00000000000..5d17f4418ed
--- /dev/null
+++ b/app/models/ci/build.rb
@@ -0,0 +1,309 @@
+# == 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 :runner, class_name: 'Ci::Runner'
+ belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
+ belongs_to :user
+
+ serialize :options
+
+ validates :commit, presence: true
+ validates :status, presence: true
+ validates :coverage, numericality: true, allow_blank: true
+ validates_presence_of :ref
+
+ 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]) }
+ scope :latest, ->() { where(id: unscope(:select).select('max(id)').group(:name, :ref)).order(stage_idx: :asc) }
+ scope :ignore_failures, ->() { where(allow_failure: false) }
+ scope :for_ref, ->(ref) { where(ref: ref) }
+ scope :similar, ->(build) { where(ref: build.ref, tag: build.tag, trigger_request_id: build.trigger_request_id) }
+
+ 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.ref = build.ref
+ new_build.tag = build.tag
+ 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.name = build.name
+ new_build.allow_failure = build.allow_failure
+ new_build.stage = build.stage
+ new_build.stage_idx = build.stage_idx
+ 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.should_create_next_builds?(build)
+ build.commit.create_next_builds(build.ref, build.tag, build.user, 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, :project, :gl_project,
+ to: :commit, prefix: false
+
+ def before_sha
+ Gitlab::Git::BLANK_SHA
+ end
+
+ def trace_html
+ html = Ci::Ansi2html::convert(trace) if trace.present?
+ html || ''
+ 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 project_recipients
+ recipients = project.email_recipients.split(' ')
+
+ if project.email_add_pusher? && user.present? && user.notification_email.present?
+ recipients << user.notification_email
+ end
+
+ recipients.uniq
+ 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
+ # if bad regex or something goes wrong we dont want to interrupt transition
+ # so we just silentrly ignore error for now
+ end
+ end
+
+ def raw_trace
+ if File.exist?(path_to_trace)
+ File.read(path_to_trace)
+ else
+ # backward compatibility
+ read_attribute :trace
+ end
+ end
+
+ def trace
+ trace = raw_trace
+ if project && trace.present?
+ trace.gsub(project.token, 'xxxxxx')
+ else
+ 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
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
new file mode 100644
index 00000000000..fde754a92a1
--- /dev/null
+++ b/app/models/ci/commit.rb
@@ -0,0 +1,246 @@
+# == 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 :gl_project, class_name: '::Project', foreign_key: :gl_project_id
+ has_many :builds, dependent: :destroy, class_name: 'Ci::Build'
+ has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
+
+ validates_presence_of :sha
+ validate :valid_commit_sha
+
+ def self.truncate_sha(sha)
+ sha[0...8]
+ end
+
+ def to_param
+ sha
+ end
+
+ def project
+ @project ||= gl_project.ensure_gitlab_ci_project
+ end
+
+ def project_id
+ project.id
+ 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 git_author_name
+ commit_data.author_name if commit_data
+ end
+
+ def git_author_email
+ commit_data.author_email if commit_data
+ end
+
+ def git_commit_message
+ commit_data.message if commit_data
+ end
+
+ def short_sha
+ Ci::Commit.truncate_sha(sha)
+ end
+
+ def commit_data
+ @commit ||= gl_project.commit(sha)
+ rescue
+ nil
+ end
+
+ def stage
+ running_or_pending = builds_without_retry.running_or_pending
+ running_or_pending.limit(1).pluck(:stage).first
+ end
+
+ def create_builds(ref, tag, user, trigger_request = nil)
+ return if skip_ci? && trigger_request.blank?
+ return unless config_processor
+ config_processor.stages.any? do |stage|
+ CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present?
+ end
+ end
+
+ def create_next_builds(ref, tag, user, trigger_request)
+ return if skip_ci? && trigger_request.blank?
+ return unless config_processor
+
+ stages = builds.where(ref: ref, tag: tag, trigger_request: trigger_request).group_by(&:stage)
+
+ config_processor.stages.any? do |stage|
+ unless stages.include?(stage)
+ CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present?
+ end
+ end
+ end
+
+ def refs
+ builds.group(:ref).pluck(:ref)
+ end
+
+ def last_ref
+ builds.latest.first.try(:ref)
+ end
+
+ def builds_without_retry
+ builds.latest
+ end
+
+ def builds_without_retry_for_ref(ref)
+ builds.for_ref(ref).latest
+ 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 duration_for_ref(ref)
+ builds_without_retry_for_ref(ref).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_for_ref?(ref)
+ builds_without_retry_for_ref(ref).pluck(:id).size > 1
+ end
+
+ def config_processor
+ @config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file)
+ 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 ci_yaml_file
+ gl_project.repository.blob_at(sha, '.gitlab-ci.yml').data
+ rescue
+ nil
+ end
+
+ def skip_ci?
+ return false if builds.any?
+ git_commit_message =~ /(\[ci skip\])/ if git_commit_message
+ end
+
+ def update_committed!
+ update!(committed_at: DateTime.now)
+ end
+
+ def should_create_next_builds?(build)
+ # don't create other builds if this one is retried
+ other_builds = builds.similar(build).latest
+ return false unless other_builds.include?(build)
+
+ other_builds.all? do |build|
+ build.success? || build.ignored?
+ end
+ end
+
+ private
+
+ def save_yaml_error(error)
+ return if self.yaml_errors?
+ self.yaml_errors = error
+ save
+ end
+ end
+end
diff --git a/app/models/ci/event.rb b/app/models/ci/event.rb
new file mode 100644
index 00000000000..cac3a7a49c1
--- /dev/null
+++ b/app/models/ci/event.rb
@@ -0,0 +1,27 @@
+# == 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
diff --git a/app/models/ci/project.rb b/app/models/ci/project.rb
new file mode 100644
index 00000000000..88ba933a434
--- /dev/null
+++ b/app/models/ci/project.rb
@@ -0,0 +1,215 @@
+# == Schema Information
+#
+# Table name: projects
+#
+# id :integer not null, primary key
+# name :string(255) not null
+# timeout :integer default(3600), not null
+# created_at :datetime
+# updated_at :datetime
+# token :string(255)
+# default_ref :string(255)
+# path :string(255)
+# always_build :boolean default(FALSE), not null
+# polling_interval :integer
+# public :boolean default(FALSE), not null
+# ssh_url_to_repo :string(255)
+# gitlab_id :integer
+# allow_git_fetch :boolean default(TRUE), not null
+# email_recipients :string(255) default(""), not null
+# email_add_pusher :boolean default(TRUE), not null
+# email_only_broken_builds :boolean default(TRUE), not null
+# skip_refs :string(255)
+# coverage_regex :string(255)
+# shared_runners_enabled :boolean default(FALSE)
+# generated_yaml_config :text
+#
+
+module Ci
+ class Project < ActiveRecord::Base
+ extend Ci::Model
+
+ include Ci::ProjectStatus
+
+ belongs_to :gl_project, class_name: '::Project', foreign_key: :gitlab_id
+
+ has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
+ has_many :runners, through: :runner_projects, class_name: 'Ci::Runner'
+ has_many :web_hooks, dependent: :destroy, class_name: 'Ci::WebHook'
+ has_many :events, dependent: :destroy, class_name: 'Ci::Event'
+ has_many :variables, dependent: :destroy, class_name: 'Ci::Variable'
+ has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger'
+
+ # Project services
+ has_many :services, dependent: :destroy, class_name: 'Ci::Service'
+ has_one :hip_chat_service, dependent: :destroy, class_name: 'Ci::HipChatService'
+ has_one :slack_service, dependent: :destroy, class_name: 'Ci::SlackService'
+ has_one :mail_service, dependent: :destroy, class_name: 'Ci::MailService'
+
+ accepts_nested_attributes_for :variables, allow_destroy: true
+
+ delegate :name_with_namespace, :path_with_namespace, :web_url, :http_url_to_repo, :ssh_url_to_repo, to: :gl_project
+
+ #
+ # Validations
+ #
+ validates_presence_of :timeout, :token, :default_ref, :gitlab_id
+
+ validates_uniqueness_of :gitlab_id
+
+ validates :polling_interval,
+ presence: true,
+ if: ->(project) { project.always_build.present? }
+
+ before_validation :set_default_values
+
+ class << self
+ include Ci::CurrentSettings
+
+ def base_build_script
+ <<-eos
+ git submodule update --init
+ ls -la
+ eos
+ end
+
+ def parse(project)
+ params = {
+ gitlab_id: project.id,
+ default_ref: project.default_branch || 'master',
+ email_add_pusher: current_application_settings.add_pusher,
+ email_only_broken_builds: current_application_settings.all_broken_builds,
+ }
+
+ project = Ci::Project.new(params)
+ project.build_missing_services
+ project
+ end
+
+ def already_added?(project)
+ where(gitlab_id: project.id).any?
+ end
+
+ def unassigned(runner)
+ joins("LEFT JOIN #{Ci::RunnerProject.table_name} ON #{Ci::RunnerProject.table_name}.project_id = #{Ci::Project.table_name}.id " \
+ "AND #{Ci::RunnerProject.table_name}.runner_id = #{runner.id}").
+ where("#{Ci::RunnerProject.table_name}.project_id" => nil)
+ end
+
+ def ordered_by_last_commit_date
+ last_commit_subquery = "(SELECT gl_project_id, MAX(committed_at) committed_at FROM #{Ci::Commit.table_name} GROUP BY gl_project_id)"
+ joins("LEFT JOIN #{last_commit_subquery} AS last_commit ON #{Ci::Project.table_name}.gitlab_id = last_commit.gl_project_id").
+ order("CASE WHEN last_commit.committed_at IS NULL THEN 1 ELSE 0 END, last_commit.committed_at DESC")
+ end
+ end
+
+ def name
+ name_with_namespace
+ end
+
+ def path
+ path_with_namespace
+ end
+
+ def gitlab_url
+ web_url
+ end
+
+ def any_runners?
+ if runners.active.any?
+ return true
+ end
+
+ shared_runners_enabled && Ci::Runner.shared.active.any?
+ end
+
+ def set_default_values
+ self.token = SecureRandom.hex(15) if self.token.blank?
+ self.default_ref ||= 'master'
+ end
+
+ def tracked_refs
+ @tracked_refs ||= default_ref.split(",").map { |ref| ref.strip }
+ end
+
+ def valid_token? token
+ self.token && self.token == token
+ end
+
+ def no_running_builds?
+ # Get running builds not later than 3 days ago to ignore hangs
+ builds.running.where("updated_at > ?", 3.days.ago).empty?
+ end
+
+ def email_notification?
+ email_add_pusher || email_recipients.present?
+ end
+
+ def web_hooks?
+ web_hooks.any?
+ end
+
+ def services?
+ services.any?
+ end
+
+ def timeout_in_minutes
+ timeout / 60
+ end
+
+ def timeout_in_minutes=(value)
+ self.timeout = value.to_i * 60
+ end
+
+ def coverage_enabled?
+ coverage_regex.present?
+ end
+
+ # Build a clone-able repo url
+ # using http and basic auth
+ def repo_url_with_auth
+ auth = "gitlab-ci-token:#{token}@"
+ http_url_to_repo.sub(/^https?:\/\//) do |prefix|
+ prefix + auth
+ end
+ end
+
+ def available_services_names
+ %w(slack mail hip_chat)
+ end
+
+ def build_missing_services
+ available_services_names.each do |service_name|
+ service = services.find { |service| service.to_param == service_name }
+
+ # If service is available but missing in db
+ # we should create an instance. Ex `create_gitlab_ci_service`
+ self.send :"create_#{service_name}_service" if service.nil?
+ end
+ end
+
+ def execute_services(data)
+ services.each do |service|
+
+ # Call service hook only if it is active
+ begin
+ service.execute(data) if service.active && service.can_execute?(data)
+ rescue => e
+ logger.error(e)
+ end
+ end
+ end
+
+ def setup_finished?
+ commits.any?
+ end
+
+ def commits
+ gl_project.ci_commits
+ end
+
+ def builds
+ gl_project.ci_builds
+ end
+ end
+end
diff --git a/app/models/ci/project_status.rb b/app/models/ci/project_status.rb
new file mode 100644
index 00000000000..b66f1212f23
--- /dev/null
+++ b/app/models/ci/project_status.rb
@@ -0,0 +1,35 @@
+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
+
+ def last_commit_for_ref(ref)
+ commits.where(ref: ref).last
+ end
+ end
+end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
new file mode 100644
index 00000000000..6838ccfaaab
--- /dev/null
+++ b/app/models/ci/runner.rb
@@ -0,0 +1,84 @@
+# == Schema Information
+#
+# Table name: runners
+#
+# id :integer not null, primary key
+# token :string(255)
+# created_at :datetime
+# updated_at :datetime
+# description :string(255)
+# contacted_at :datetime
+# active :boolean default(TRUE), not null
+# is_shared :boolean default(FALSE)
+# name :string(255)
+# version :string(255)
+# revision :string(255)
+# platform :string(255)
+# architecture :string(255)
+#
+
+module Ci
+ class Runner < ActiveRecord::Base
+ extend Ci::Model
+
+ has_many :builds, class_name: 'Ci::Build'
+ has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
+ has_many :projects, through: :runner_projects, class_name: 'Ci::Project'
+
+ has_one :last_build, ->() { order('id DESC') }, class_name: 'Ci::Build'
+
+ before_validation :set_default_values
+
+ scope :specific, ->() { where(is_shared: false) }
+ scope :shared, ->() { where(is_shared: true) }
+ scope :active, ->() { where(active: true) }
+ scope :paused, ->() { where(active: false) }
+
+ acts_as_taggable
+
+ def self.search(query)
+ where('LOWER(ci_runners.token) LIKE :query OR LOWER(ci_runners.description) like :query',
+ query: "%#{query.try(:downcase)}%")
+ end
+
+ def gl_projects_ids
+ projects.select(:gitlab_id)
+ end
+
+ def set_default_values
+ self.token = SecureRandom.hex(15) if self.token.blank?
+ end
+
+ def assign_to(project, current_user = nil)
+ self.is_shared = false if shared?
+ self.save
+ project.runner_projects.create!(runner_id: self.id)
+ end
+
+ def display_name
+ return token unless !description.blank?
+
+ description
+ end
+
+ def shared?
+ is_shared
+ end
+
+ def belongs_to_one_project?
+ runner_projects.count == 1
+ end
+
+ def specific?
+ !shared?
+ end
+
+ def only_for?(project)
+ projects == [project]
+ end
+
+ def short_sha
+ token[0...10]
+ end
+ end
+end
diff --git a/app/models/ci/runner_project.rb b/app/models/ci/runner_project.rb
new file mode 100644
index 00000000000..44453ee4b41
--- /dev/null
+++ b/app/models/ci/runner_project.rb
@@ -0,0 +1,21 @@
+# == 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
diff --git a/app/models/ci/service.rb b/app/models/ci/service.rb
new file mode 100644
index 00000000000..ed5e3f940b6
--- /dev/null
+++ b/app/models/ci/service.rb
@@ -0,0 +1,105 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer not null
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+#
+
+# To add new service you should build a class inherited from Service
+# and implement a set of methods
+module Ci
+ class Service < ActiveRecord::Base
+ extend Ci::Model
+
+ serialize :properties, JSON
+
+ default_value_for :active, false
+
+ after_initialize :initialize_properties
+
+ belongs_to :project, class_name: 'Ci::Project'
+
+ validates :project_id, presence: true
+
+ def activated?
+ active
+ end
+
+ def category
+ :common
+ end
+
+ def initialize_properties
+ self.properties = {} if properties.nil?
+ end
+
+ def title
+ # implement inside child
+ end
+
+ def description
+ # implement inside child
+ end
+
+ def help
+ # implement inside child
+ end
+
+ def to_param
+ # implement inside child
+ end
+
+ def fields
+ # implement inside child
+ []
+ end
+
+ def can_test?
+ project.builds.any?
+ end
+
+ def can_execute?(build)
+ true
+ end
+
+ def execute(build)
+ # implement inside child
+ end
+
+ # Provide convenient accessor methods
+ # for each serialized property.
+ def self.prop_accessor(*args)
+ args.each do |arg|
+ class_eval %{
+ def #{arg}
+ (properties || {})['#{arg}']
+ end
+
+ def #{arg}=(value)
+ self.properties ||= {}
+ self.properties['#{arg}'] = value
+ end
+ }
+ end
+ end
+
+ def self.boolean_accessor(*args)
+ self.prop_accessor(*args)
+
+ args.each do |arg|
+ class_eval %{
+ def #{arg}?
+ ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(#{arg})
+ end
+ }
+ end
+ end
+ end
+end
diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb
new file mode 100644
index 00000000000..fe224b7dc70
--- /dev/null
+++ b/app/models/ci/trigger.rb
@@ -0,0 +1,39 @@
+# == 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
diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb
new file mode 100644
index 00000000000..29cd9553394
--- /dev/null
+++ b/app/models/ci/trigger_request.rb
@@ -0,0 +1,23 @@
+# == Schema Information
+#
+# Table name: trigger_requests
+#
+# id :integer not null, primary key
+# trigger_id :integer not null
+# variables :text
+# created_at :datetime
+# updated_at :datetime
+# commit_id :integer
+#
+
+module Ci
+ class TriggerRequest < ActiveRecord::Base
+ extend Ci::Model
+
+ belongs_to :trigger, class_name: 'Ci::Trigger'
+ belongs_to :commit, class_name: 'Ci::Commit'
+ has_many :builds, class_name: 'Ci::Build'
+
+ serialize :variables
+ end
+end
diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb
new file mode 100644
index 00000000000..7a542802fa6
--- /dev/null
+++ b/app/models/ci/variable.rb
@@ -0,0 +1,25 @@
+# == Schema Information
+#
+# Table name: variables
+#
+# id :integer not null, primary key
+# project_id :integer not null
+# key :string(255)
+# value :text
+# encrypted_value :text
+# encrypted_value_salt :string(255)
+# encrypted_value_iv :string(255)
+#
+
+module Ci
+ class Variable < ActiveRecord::Base
+ extend Ci::Model
+
+ belongs_to :project, class_name: 'Ci::Project'
+
+ validates_presence_of :key
+ validates_uniqueness_of :key, scope: :project_id
+
+ attr_encrypted :value, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base
+ end
+end
diff --git a/app/models/ci/web_hook.rb b/app/models/ci/web_hook.rb
new file mode 100644
index 00000000000..8f03b0625da
--- /dev/null
+++ b/app/models/ci/web_hook.rb
@@ -0,0 +1,44 @@
+# == Schema Information
+#
+# Table name: web_hooks
+#
+# id :integer not null, primary key
+# url :string(255) not null
+# project_id :integer not null
+# created_at :datetime
+# updated_at :datetime
+#
+
+module Ci
+ class WebHook < ActiveRecord::Base
+ extend Ci::Model
+
+ include HTTParty
+
+ belongs_to :project, class_name: 'Ci::Project'
+
+ # HTTParty timeout
+ default_timeout 10
+
+ validates :url, presence: true,
+ format: { with: URI::regexp(%w(http https)), message: "should be a valid url" }
+
+ def execute(data)
+ parsed_url = URI.parse(url)
+ if parsed_url.userinfo.blank?
+ Ci::WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }, verify: false)
+ else
+ post_url = url.gsub("#{parsed_url.userinfo}@", "")
+ auth = {
+ username: URI.decode(parsed_url.user),
+ password: URI.decode(parsed_url.password),
+ }
+ Ci::WebHook.post(post_url,
+ body: data.to_json,
+ headers: { "Content-Type" => "application/json" },
+ verify: false,
+ basic_auth: auth)
+ end
+ end
+ end
+end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 40642dc63ba..4db4ffb2e79 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -140,6 +140,12 @@ module Issuable
{
object_kind: self.class.name.underscore,
user: user.hook_attrs,
+ repository: {
+ name: project.name,
+ url: project.url_to_repo,
+ description: project.description,
+ homepage: project.web_url
+ },
object_attributes: hook_attrs
}
end
diff --git a/app/models/event.rb b/app/models/event.rb
index 78f16c6304e..47600c57e35 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -27,6 +27,7 @@ class Event < ActiveRecord::Base
MERGED = 7
JOINED = 8 # User joined project
LEFT = 9 # User left project
+ DESTROYED = 10
delegate :name, :email, to: :author, prefix: true, allow_nil: true
delegate :title, to: :issue, prefix: true, allow_nil: true
@@ -48,6 +49,7 @@ class Event < ActiveRecord::Base
scope :code_push, -> { where(action: PUSHED) }
scope :in_projects, ->(project_ids) { where(project_id: project_ids).recent }
scope :with_associations, -> { includes(project: :namespace) }
+ scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) }
class << self
def reset_event_cache_for(target)
@@ -71,7 +73,7 @@ class Event < ActiveRecord::Base
elsif created_project?
true
else
- (issue? || merge_request? || note? || milestone?) && target
+ ((issue? || merge_request? || note?) && target) || milestone?
end
end
@@ -115,6 +117,10 @@ class Event < ActiveRecord::Base
action == LEFT
end
+ def destroyed?
+ action == DESTROYED
+ end
+
def commented?
action == COMMENTED
end
@@ -124,7 +130,7 @@ class Event < ActiveRecord::Base
end
def created_project?
- created? && !target
+ created? && !target && target_type.nil?
end
def created_target?
@@ -180,6 +186,8 @@ class Event < ActiveRecord::Base
'joined'
elsif left?
'left'
+ elsif destroyed?
+ 'destroyed'
elsif commented?
"commented on"
elsif created_project?
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 9a8251bdad5..a078accbdbd 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -25,7 +25,7 @@ class WebHook < ActiveRecord::Base
default_value_for :note_events, false
default_value_for :merge_requests_events, false
default_value_for :tag_push_events, false
- default_value_for :enable_ssl_verification, false
+ default_value_for :enable_ssl_verification, true
# HTTParty timeout
default_timeout Gitlab.config.gitlab.webhook_timeout
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 2456b7d0dc1..fc7e9abe29e 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -2,19 +2,20 @@
#
# Table name: issues
#
-# id :integer not null, primary key
-# title :string(255)
-# assignee_id :integer
-# author_id :integer
-# project_id :integer
-# created_at :datetime
-# updated_at :datetime
-# position :integer default(0)
-# branch_name :string(255)
-# description :text
-# milestone_id :integer
-# state :string(255)
-# iid :integer
+# id :integer not null, primary key
+# title :string(255)
+# assignee_id :integer
+# author_id :integer
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# position :integer default(0)
+# branch_name :string(255)
+# description :text
+# milestone_id :integer
+# state :string(255)
+# iid :integer
+# updated_by_id :integer
#
require 'carrierwave/orm/activerecord'
diff --git a/app/models/label.rb b/app/models/label.rb
index 230631b5180..4a22bd53400 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -24,7 +24,7 @@ class Label < ActiveRecord::Base
validates :color,
format: { with: /\A#[0-9A-Fa-f]{6}\Z/ },
allow_blank: false
- validates :project, presence: true
+ validates :project, presence: true, unless: Proc.new { |service| service.template? }
# Don't allow '?', '&', and ',' for label titles
validates :title,
@@ -34,6 +34,8 @@ class Label < ActiveRecord::Base
default_scope { order(title: :asc) }
+ scope :templates, -> { where(template: true) }
+
alias_attribute :name, :title
def self.reference_prefix
@@ -78,4 +80,8 @@ class Label < ActiveRecord::Base
def open_issues_count
issues.opened.count
end
+
+ def template?
+ template
+ end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 467b90861f9..eb468c6cd53 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -19,6 +19,7 @@
# description :text
# position :integer default(0)
# locked_at :datetime
+# updated_by_id :integer
#
require Rails.root.join("app/models/commit")
@@ -432,10 +433,22 @@ class MergeRequest < ActiveRecord::Base
target_project.repository.fetch_ref(
source_project.repository.path_to_repo,
"refs/heads/#{source_branch}",
- "refs/merge-requests/#{iid}/head"
+ ref_path
)
end
+ def ref_path
+ "refs/merge-requests/#{iid}/head"
+ end
+
+ def ref_is_fetched?
+ File.exists?(File.join(project.repository.path_to_repo, ref_path))
+ end
+
+ def ensure_ref_fetched
+ fetch_ref unless ref_is_fetched?
+ end
+
def in_locked_state
begin
lock_mr
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index e317c8eac4d..c9ef8023aea 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -123,12 +123,12 @@ class MergeRequestDiff < ActiveRecord::Base
if new_diffs.any?
if new_diffs.size > Commit::DIFF_HARD_LIMIT_FILES
self.state = :overflow_diff_files_limit
- new_diffs = new_diffs.first[Commit::DIFF_HARD_LIMIT_LINES]
+ new_diffs = new_diffs.first(Commit::DIFF_HARD_LIMIT_LINES)
end
if new_diffs.sum { |diff| diff.diff.lines.count } > Commit::DIFF_HARD_LIMIT_LINES
self.state = :overflow_diff_lines_limit
- new_diffs = new_diffs.first[Commit::DIFF_HARD_LIMIT_LINES]
+ new_diffs = new_diffs.first(Commit::DIFF_HARD_LIMIT_LINES)
end
end
@@ -144,12 +144,10 @@ class MergeRequestDiff < ActiveRecord::Base
# Collect array of Git::Diff objects
# between target and source branches
def unmerged_diffs
- diffs = compare_result.diffs
- diffs ||= []
- diffs
- rescue Gitlab::Git::Diff::TimeoutError => ex
+ compare_result.diffs || []
+ rescue Gitlab::Git::Diff::TimeoutError
self.state = :timeout
- diffs = []
+ []
end
def repository
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index c6aff6f709f..d979a35084b 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -61,7 +61,7 @@ class Milestone < ActiveRecord::Base
false
end
end
-
+
def open_items_count
self.issues.opened.count + self.merge_requests.opened.count
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 161a16ca61c..bc8525df5a5 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -137,7 +137,9 @@ class Namespace < ActiveRecord::Base
end
def send_update_instructions
- projects.each(&:send_move_instructions)
+ projects.each do |project|
+ project.send_move_instructions("#{path_was}/#{project.path}")
+ end
end
def kind
diff --git a/app/models/note.rb b/app/models/note.rb
index 36cad8f583d..de3b6df88f7 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -15,6 +15,7 @@
# noteable_id :integer
# system :boolean default(FALSE), not null
# st_diff :text
+# updated_by_id :integer
#
require 'carrierwave/orm/activerecord'
@@ -365,6 +366,6 @@ class Note < ActiveRecord::Base
end
def editable?
- !read_attribute(:system)
+ !system?
end
end
diff --git a/app/models/notification.rb b/app/models/notification.rb
index 1395274173d..171b8df45c2 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -12,7 +12,7 @@ class Notification
class << self
def notification_levels
- [N_DISABLED, N_PARTICIPATING, N_WATCH, N_MENTION]
+ [N_DISABLED, N_MENTION, N_PARTICIPATING, N_WATCH]
end
def options_with_labels
@@ -26,7 +26,7 @@ class Notification
end
def project_notification_levels
- [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL, N_MENTION]
+ [N_DISABLED, N_MENTION, N_PARTICIPATING, N_WATCH, N_GLOBAL]
end
end
@@ -57,4 +57,21 @@ class Notification
def level
target.notification_level
end
+
+ def to_s
+ case level
+ when N_DISABLED
+ 'Disabled'
+ when N_PARTICIPATING
+ 'Participating'
+ when N_WATCH
+ 'Watching'
+ when N_MENTION
+ 'On mention'
+ when N_GLOBAL
+ 'Global'
+ else
+ # do nothing
+ end
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 69f9af91c51..bb47b9abb03 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -39,10 +39,13 @@ class Project < ActiveRecord::Base
include Gitlab::VisibilityLevel
include Referable
include Sortable
+ include AfterCommitQueue
extend Gitlab::ConfigHelper
extend Enumerize
+ UNKNOWN_IMPORT_URL = 'http://unknown.git'
+
default_value_for :archived, false
default_value_for :visibility_level, gitlab_config_features.visibility_level
default_value_for :issues_enabled, gitlab_config_features.issues
@@ -73,6 +76,7 @@ class Project < ActiveRecord::Base
has_many :services
has_one :gitlab_ci_service, dependent: :destroy
has_one :campfire_service, dependent: :destroy
+ has_one :drone_ci_service, dependent: :destroy
has_one :emails_on_push_service, dependent: :destroy
has_one :irker_service, dependent: :destroy
has_one :pivotaltracker_service, dependent: :destroy
@@ -114,8 +118,11 @@ class Project < ActiveRecord::Base
has_many :deploy_keys, through: :deploy_keys_projects
has_many :users_star_projects, dependent: :destroy
has_many :starrers, through: :users_star_projects, source: :user
+ has_many :ci_commits, ->() { order('CASE WHEN ci_commits.committed_at IS NULL THEN 0 ELSE 1 END', :committed_at, :id) }, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id
+ has_many :ci_builds, through: :ci_commits, source: :builds, dependent: :destroy, class_name: 'Ci::Build'
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
+ has_one :gitlab_ci_project, dependent: :destroy, class_name: "Ci::Project", foreign_key: :gitlab_id
delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true
@@ -141,7 +148,7 @@ class Project < ActiveRecord::Base
validates_uniqueness_of :path, scope: :namespace_id
validates :import_url,
format: { with: /\A#{URI.regexp(%w(ssh git http https))}\z/, message: 'should be a valid url' },
- if: :import?
+ if: :external_import?
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
validate :avatar_type,
@@ -187,7 +194,7 @@ class Project < ActiveRecord::Base
state :finished
state :failed
- after_transition any => :started, do: :add_import_job
+ after_transition any => :started, do: :schedule_add_import_job
after_transition any => :finished, do: :clear_import_data
end
@@ -231,10 +238,10 @@ class Project < ActiveRecord::Base
return nil unless id.include?('/')
id = id.split('/')
- namespace = Namespace.find_by(path: id.first)
+ namespace = Namespace.by_path(id.first)
return nil unless namespace
- where(namespace_id: namespace.id).find_by(path: id.second)
+ where(namespace_id: namespace.id).where("LOWER(projects.path) = :path", path: id.second.downcase).first
end
def visibility_levels
@@ -271,8 +278,18 @@ class Project < ActiveRecord::Base
id && persisted?
end
+ def schedule_add_import_job
+ run_after_commit(:add_import_job)
+ end
+
def add_import_job
- RepositoryImportWorker.perform_in(2.seconds, id)
+ if forked?
+ unless RepositoryForkWorker.perform_async(id, forked_from_project.path_with_namespace, self.namespace.path)
+ import_fail
+ end
+ else
+ RepositoryImportWorker.perform_async(id)
+ end
end
def clear_import_data
@@ -280,6 +297,10 @@ class Project < ActiveRecord::Base
end
def import?
+ external_import? || forked?
+ end
+
+ def external_import?
import_url.present?
end
@@ -316,7 +337,7 @@ class Project < ActiveRecord::Base
end
def web_url
- Rails.application.routes.url_helpers.namespace_project_url(self.namespace, self)
+ Gitlab::Application.routes.url_helpers.namespace_project_url(self.namespace, self)
end
def web_url_without_protocol
@@ -392,7 +413,7 @@ class Project < ActiveRecord::Base
if template.nil?
# If no template, we should create an instance. Ex `create_gitlab_ci_service`
- service = self.send :"create_#{service_name}_service"
+ self.send :"create_#{service_name}_service"
else
Service.create_from_template(self.id, template)
end
@@ -400,12 +421,21 @@ class Project < ActiveRecord::Base
end
end
+ def create_labels
+ Label.templates.each do |label|
+ label = label.dup
+ label.template = nil
+ label.project_id = self.id
+ label.save
+ end
+ end
+
def find_service(list, name)
list.find { |service| service.to_param == name }
end
def gitlab_ci?
- gitlab_ci_service && gitlab_ci_service.active
+ gitlab_ci_service && gitlab_ci_service.active && gitlab_ci_project.present?
end
def ci_services
@@ -433,7 +463,7 @@ class Project < ActiveRecord::Base
if avatar.present?
[gitlab_config.url, avatar.url].join
elsif avatar_in_git
- Rails.application.routes.url_helpers.namespace_project_avatar_url(namespace, self)
+ Gitlab::Application.routes.url_helpers.namespace_project_avatar_url(namespace, self)
end
end
@@ -451,8 +481,8 @@ class Project < ActiveRecord::Base
end
end
- def send_move_instructions
- NotificationService.new.project_was_moved(self)
+ def send_move_instructions(old_path_with_namespace)
+ NotificationService.new.project_was_moved(self, old_path_with_namespace)
end
def owner
@@ -594,7 +624,7 @@ class Project < ActiveRecord::Base
# So we basically we mute exceptions in next actions
begin
gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
- send_move_instructions
+ send_move_instructions(old_path_with_namespace)
reset_events_cache
rescue
# Returning false does not rollback after_* transaction but gives
@@ -613,6 +643,7 @@ class Project < ActiveRecord::Base
name: name,
ssh_url: ssh_url_to_repo,
http_url: http_url_to_repo,
+ web_url: web_url,
namespace: namespace.name,
visibility_level: visibility_level
}
@@ -689,14 +720,8 @@ class Project < ActiveRecord::Base
end
def create_repository
- if forked?
- if gitlab_shell.fork_repository(forked_from_project.path_with_namespace, self.namespace.path)
- true
- else
- errors.add(:base, 'Failed to fork repository via gitlab-shell')
- false
- end
- else
+ # Forked import is handled asynchronously
+ unless forked?
if gitlab_shell.add_repository(path_with_namespace)
true
else
@@ -713,8 +738,26 @@ class Project < ActiveRecord::Base
def create_wiki
ProjectWiki.new(self, self.owner).wiki
true
- rescue ProjectWiki::CouldNotCreateWikiError => ex
+ rescue ProjectWiki::CouldNotCreateWikiError
errors.add(:base, 'Failed create wiki')
false
end
+
+ def ci_commit(sha)
+ ci_commits.find_by(sha: sha)
+ end
+
+ def ensure_ci_commit(sha)
+ ci_commit(sha) || ci_commits.create(sha: sha)
+ end
+
+ def ensure_gitlab_ci_project
+ gitlab_ci_project || create_gitlab_ci_project
+ end
+
+ def enable_ci
+ service = gitlab_ci_service || create_gitlab_ci_service
+ service.active = true
+ service.save
+ end
end
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index 9e5da6f45d2..40058b53df5 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -69,14 +69,6 @@ class BuildkiteService < CiService
"#{project_url}/builds?commit=#{sha}"
end
- def builds_path
- "#{project_url}/builds?branch=#{project.default_branch}"
- end
-
- def status_img_path
- "#{buildkite_endpoint('badge')}/#{status_token}.svg"
- end
-
def title
'Buildkite'
end
diff --git a/app/models/project_services/ci/hip_chat_message.rb b/app/models/project_services/ci/hip_chat_message.rb
new file mode 100644
index 00000000000..cbf325cc525
--- /dev/null
+++ b/app/models/project_services/ci/hip_chat_message.rb
@@ -0,0 +1,73 @@
+module Ci
+ class HipChatMessage
+ include Gitlab::Application.routes.url_helpers
+
+ attr_reader :build
+
+ def initialize(build)
+ @build = build
+ end
+
+ def to_s
+ lines = Array.new
+ lines.push("<a href=\"#{ci_project_url(project)}\">#{project.name}</a> - ")
+ lines.push("<a href=\"#{ci_namespace_project_commit_url(commit.gl_project.namespace, commit.gl_project, commit.sha)}\">Commit ##{commit.id}</a></br>")
+ lines.push("#{commit.short_sha} #{commit.git_author_name} - #{commit.git_commit_message}</br>")
+ lines.push("#{humanized_status(commit_status)} in #{commit.duration} second(s).")
+ lines.join('')
+ end
+
+ def status_color(build_or_commit=nil)
+ build_or_commit ||= commit_status
+ case build_or_commit
+ when :success
+ 'green'
+ when :failed, :canceled
+ 'red'
+ else # :pending, :running or unknown
+ 'yellow'
+ end
+ end
+
+ def notify?
+ [:failed, :canceled].include?(commit_status)
+ end
+
+
+ private
+
+ def commit
+ build.commit
+ end
+
+ def project
+ commit.project
+ end
+
+ def build_status
+ build.status.to_sym
+ end
+
+ def commit_status
+ commit.status.to_sym
+ end
+
+ def humanized_status(build_or_commit=nil)
+ build_or_commit ||= commit_status
+ case build_or_commit
+ when :pending
+ "Pending"
+ when :running
+ "Running"
+ when :failed
+ "Failed"
+ when :success
+ "Successful"
+ when :canceled
+ "Canceled"
+ else
+ "Unknown"
+ end
+ end
+ end
+end
diff --git a/app/models/project_services/ci/hip_chat_service.rb b/app/models/project_services/ci/hip_chat_service.rb
new file mode 100644
index 00000000000..0e6e97394bc
--- /dev/null
+++ b/app/models/project_services/ci/hip_chat_service.rb
@@ -0,0 +1,93 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer not null
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+#
+
+module Ci
+ class HipChatService < Ci::Service
+ prop_accessor :hipchat_token, :hipchat_room, :hipchat_server
+ boolean_accessor :notify_only_broken_builds
+ validates :hipchat_token, presence: true, if: :activated?
+ validates :hipchat_room, presence: true, if: :activated?
+ default_value_for :notify_only_broken_builds, true
+
+ def title
+ "HipChat"
+ end
+
+ def description
+ "Private group chat, video chat, instant messaging for teams"
+ end
+
+ def help
+ end
+
+ def to_param
+ 'hip_chat'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'hipchat_token', label: 'Token', placeholder: '' },
+ { type: 'text', name: 'hipchat_room', label: 'Room', placeholder: '' },
+ { type: 'text', name: 'hipchat_server', label: 'Server', placeholder: 'https://hipchat.example.com', help: 'Leave blank for default' },
+ { type: 'checkbox', name: 'notify_only_broken_builds', label: 'Notify only broken builds' }
+ ]
+ end
+
+ def can_execute?(build)
+ return if build.allow_failure?
+
+ commit = build.commit
+ return unless commit
+ return unless commit.builds_without_retry.include? build
+
+ case commit.status.to_sym
+ when :failed
+ true
+ when :success
+ true unless notify_only_broken_builds?
+ else
+ false
+ end
+ end
+
+ def execute(build)
+ msg = Ci::HipChatMessage.new(build)
+ opts = default_options.merge(
+ token: hipchat_token,
+ room: hipchat_room,
+ server: server_url,
+ color: msg.status_color,
+ notify: msg.notify?
+ )
+ Ci::HipChatNotifierWorker.perform_async(msg.to_s, opts)
+ end
+
+ private
+
+ def default_options
+ {
+ service_name: 'GitLab CI',
+ message_format: 'html'
+ }
+ end
+
+ def server_url
+ if hipchat_server.blank?
+ 'https://api.hipchat.com'
+ else
+ hipchat_server
+ end
+ end
+ end
+end
diff --git a/app/models/project_services/ci/mail_service.rb b/app/models/project_services/ci/mail_service.rb
new file mode 100644
index 00000000000..11a2743f969
--- /dev/null
+++ b/app/models/project_services/ci/mail_service.rb
@@ -0,0 +1,84 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer not null
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+#
+
+module Ci
+ class MailService < Ci::Service
+ delegate :email_recipients, :email_recipients=,
+ :email_add_pusher, :email_add_pusher=,
+ :email_only_broken_builds, :email_only_broken_builds=, to: :project, prefix: false
+
+ before_save :update_project
+
+ default_value_for :active, true
+
+ def title
+ 'Mail'
+ end
+
+ def description
+ 'Email notification'
+ end
+
+ def to_param
+ 'mail'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'email_recipients', label: 'Recipients', help: 'Whitespace-separated list of recipient addresses' },
+ { type: 'checkbox', name: 'email_add_pusher', label: 'Add pusher to recipients list' },
+ { type: 'checkbox', name: 'email_only_broken_builds', label: 'Notify only broken builds' }
+ ]
+ end
+
+ def can_execute?(build)
+ return if build.allow_failure?
+
+ # it doesn't make sense to send emails for retried builds
+ commit = build.commit
+ return unless commit
+ return unless commit.builds_without_retry.include?(build)
+
+ case build.status.to_sym
+ when :failed
+ true
+ when :success
+ true unless email_only_broken_builds
+ else
+ false
+ end
+ end
+
+ def execute(build)
+ build.project_recipients.each do |recipient|
+ case build.status.to_sym
+ when :success
+ mailer.build_success_email(build.id, recipient)
+ when :failed
+ mailer.build_fail_email(build.id, recipient)
+ end
+ end
+ end
+
+ private
+
+ def update_project
+ project.save!
+ end
+
+ def mailer
+ Ci::Notify.delay
+ end
+ end
+end
diff --git a/app/models/project_services/ci/slack_message.rb b/app/models/project_services/ci/slack_message.rb
new file mode 100644
index 00000000000..5ac8907ecd0
--- /dev/null
+++ b/app/models/project_services/ci/slack_message.rb
@@ -0,0 +1,92 @@
+require 'slack-notifier'
+
+module Ci
+ class SlackMessage
+ include Gitlab::Application.routes.url_helpers
+
+ def initialize(commit)
+ @commit = commit
+ end
+
+ def pretext
+ ''
+ end
+
+ def color
+ attachment_color
+ end
+
+ def fallback
+ format(attachment_message)
+ end
+
+ def attachments
+ fields = []
+
+ commit.builds_without_retry.each do |build|
+ next if build.allow_failure?
+ next unless build.failed?
+ fields << {
+ title: build.name,
+ value: "Build <#{namespace_project_build_url(build.gl_project.namespace, build.gl_project, build)}|\##{build.id}> failed in #{build.duration.to_i} second(s)."
+ }
+ end
+
+ [{
+ text: attachment_message,
+ color: attachment_color,
+ fields: fields
+ }]
+ end
+
+ private
+
+ attr_reader :commit
+
+ def attachment_message
+ out = "<#{ci_project_url(project)}|#{project_name}>: "
+ out << "Commit <#{ci_namespace_project_commit_url(commit.gl_project.namespace, commit.gl_project, commit.sha)}|\##{commit.id}> "
+ out << "(<#{commit_sha_link}|#{commit.short_sha}>) "
+ out << "of <#{commit_ref_link}|#{commit.ref}> "
+ out << "by #{commit.git_author_name} " if commit.git_author_name
+ out << "#{commit_status} in "
+ out << "#{commit.duration} second(s)"
+ end
+
+ def format(string)
+ Slack::Notifier::LinkFormatter.format(string)
+ end
+
+ def project
+ commit.project
+ end
+
+ def project_name
+ project.name
+ end
+
+ def commit_sha_link
+ "#{project.gitlab_url}/commit/#{commit.sha}"
+ end
+
+ def commit_ref_link
+ "#{project.gitlab_url}/commits/#{commit.ref}"
+ end
+
+ def attachment_color
+ if commit.success?
+ 'good'
+ else
+ 'danger'
+ end
+ end
+
+ def commit_status
+ if commit.success?
+ 'succeeded'
+ else
+ 'failed'
+ end
+ end
+ end
+end
diff --git a/app/models/project_services/ci/slack_service.rb b/app/models/project_services/ci/slack_service.rb
new file mode 100644
index 00000000000..76db573dc17
--- /dev/null
+++ b/app/models/project_services/ci/slack_service.rb
@@ -0,0 +1,81 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer not null
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+#
+
+module Ci
+ class SlackService < Ci::Service
+ prop_accessor :webhook
+ boolean_accessor :notify_only_broken_builds
+ validates :webhook, presence: true, if: :activated?
+
+ default_value_for :notify_only_broken_builds, true
+
+ def title
+ 'Slack'
+ end
+
+ def description
+ 'A team communication tool for the 21st century'
+ end
+
+ def to_param
+ 'slack'
+ end
+
+ def help
+ 'Visit https://www.slack.com/services/new/incoming-webhook. Then copy link and save project!' unless webhook.present?
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'webhook', label: 'Webhook URL', placeholder: '' },
+ { type: 'checkbox', name: 'notify_only_broken_builds', label: 'Notify only broken builds' }
+ ]
+ end
+
+ def can_execute?(build)
+ return if build.allow_failure?
+
+ commit = build.commit
+ return unless commit
+ return unless commit.builds_without_retry.include?(build)
+
+ case commit.status.to_sym
+ when :failed
+ true
+ when :success
+ true unless notify_only_broken_builds?
+ else
+ false
+ end
+ end
+
+ def execute(build)
+ message = Ci::SlackMessage.new(build.commit)
+ options = default_options.merge(
+ color: message.color,
+ fallback: message.fallback,
+ attachments: message.attachments
+ )
+ Ci::SlackNotifierWorker.perform_async(webhook, message.pretext, options)
+ end
+
+ private
+
+ def default_options
+ {
+ username: 'GitLab CI'
+ }
+ end
+ end
+end
diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb
index 803402c83ee..88186113c68 100644
--- a/app/models/project_services/ci_service.rb
+++ b/app/models/project_services/ci_service.rb
@@ -25,12 +25,24 @@ class CiService < Service
def category
:ci
end
-
+
+ def valid_token?(token)
+ self.respond_to?(:token) && self.token.present? && self.token == token
+ end
+
def supported_events
%w(push)
end
- # Return complete url to build page
+ def merge_request_page(iid, sha, ref)
+ commit_page(sha, ref)
+ end
+
+ def commit_page(sha, ref)
+ build_page(sha, ref)
+ end
+
+ # Return complete url to merge_request page
#
# Ex.
# http://jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c
@@ -45,10 +57,27 @@ class CiService < Service
#
#
# Ex.
- # @service.commit_status('13be4ac')
+ # @service.merge_request_status(9, '13be4ac', 'dev')
+ # # => 'success'
+ #
+ # @service.merge_request_status(10, '2abe4ac', 'dev)
+ # # => 'running'
+ #
+ #
+ def merge_request_status(iid, sha, ref)
+ commit_status(sha, ref)
+ end
+
+ # Return string with build status or :error symbol
+ #
+ # Allowed states: 'success', 'failed', 'running', 'pending', 'skipped'
+ #
+ #
+ # Ex.
+ # @service.commit_status('13be4ac', 'master')
# # => 'success'
#
- # @service.commit_status('2abe4ac')
+ # @service.commit_status('2abe4ac', 'dev')
# # => 'running'
#
#
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
new file mode 100644
index 00000000000..c73c4b058a1
--- /dev/null
+++ b/app/models/project_services/drone_ci_service.rb
@@ -0,0 +1,176 @@
+# == Schema Information
+#
+# Table name: services
+#
+# id :integer not null, primary key
+# type :string(255)
+# title :string(255)
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# active :boolean default(FALSE), not null
+# properties :text
+# template :boolean default(FALSE)
+# push_events :boolean default(TRUE)
+# issues_events :boolean default(TRUE)
+# merge_requests_events :boolean default(TRUE)
+# tag_push_events :boolean default(TRUE)
+# note_events :boolean default(TRUE), not null
+#
+
+class DroneCiService < CiService
+
+ prop_accessor :drone_url, :token, :enable_ssl_verification
+ validates :drone_url,
+ presence: true,
+ format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated?
+ validates :token,
+ presence: true,
+ if: :activated?
+
+ after_save :compose_service_hook, if: :activated?
+
+ def compose_service_hook
+ hook = service_hook || build_service_hook
+ hook.url = [drone_url, "/api/hook", "?owner=#{project.namespace.path}", "&name=#{project.path}", "&access_token=#{token}"].join
+ hook.enable_ssl_verification = enable_ssl_verification
+ hook.save
+ end
+
+ def execute(data)
+ case data[:object_kind]
+ when 'push'
+ service_hook.execute(data) if push_valid?(data)
+ when 'merge_request'
+ service_hook.execute(data) if merge_request_valid?(data)
+ when 'tag_push'
+ service_hook.execute(data) if tag_push_valid?(data)
+ end
+ end
+
+ def allow_target_ci?
+ true
+ end
+
+ def supported_events
+ %w(push merge_request tag_push)
+ end
+
+ def merge_request_status_path(iid, sha = nil, ref = nil)
+ url = [drone_url,
+ "gitlab/#{project.namespace.path}/#{project.path}/pulls/#{iid}",
+ "?access_token=#{token}"]
+
+ URI.join(*url).to_s
+ end
+
+ def commit_status_path(sha, ref)
+ url = [drone_url,
+ "gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}",
+ "?branch=#{URI::encode(ref.to_s)}&access_token=#{token}"]
+
+ URI.join(*url).to_s
+ end
+
+ def merge_request_status(iid, sha, ref)
+ response = HTTParty.get(merge_request_status_path(iid), verify: enable_ssl_verification)
+
+ if response.code == 200 and response['status']
+ case response['status']
+ when 'killed'
+ :canceled
+ when 'failure', 'error'
+ # Because drone return error if some test env failed
+ :failed
+ else
+ response["status"]
+ end
+ else
+ :error
+ end
+ rescue Errno::ECONNREFUSED
+ :error
+ end
+
+ def commit_status(sha, ref)
+ response = HTTParty.get(commit_status_path(sha, ref), verify: enable_ssl_verification)
+
+ if response.code == 200 and response['status']
+ case response['status']
+ when 'killed'
+ :canceled
+ when 'failure', 'error'
+ # Because drone return error if some test env failed
+ :failed
+ else
+ response["status"]
+ end
+ else
+ :error
+ end
+ rescue Errno::ECONNREFUSED
+ :error
+ end
+
+ def merge_request_page(iid, sha, ref)
+ url = [drone_url,
+ "gitlab/#{project.namespace.path}/#{project.path}/redirect/pulls/#{iid}"]
+
+ URI.join(*url).to_s
+ end
+
+ def commit_page(sha, ref)
+ url = [drone_url,
+ "gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}",
+ "?branch=#{URI::encode(ref.to_s)}"]
+
+ URI.join(*url).to_s
+ end
+
+ def commit_coverage(sha, ref)
+ nil
+ end
+
+ def build_page(sha, ref)
+ commit_page(sha, ref)
+ end
+
+ def title
+ 'Drone CI'
+ end
+
+ def description
+ 'Drone is a Continuous Integration platform built on Docker, written in Go'
+ end
+
+ def to_param
+ 'drone_ci'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'token', placeholder: 'Drone CI project specific token' },
+ { type: 'text', name: 'drone_url', placeholder: 'http://drone.example.com' },
+ { type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" }
+ ]
+ end
+
+ private
+
+ def tag_push_valid?(data)
+ data[:total_commits_count] > 0 && !Gitlab::Git.blank_ref?(data[:after])
+ end
+
+ def push_valid?(data)
+ opened_merge_requests = project.merge_requests.opened.where(source_project_id: project.id,
+ source_branch: Gitlab::Git.ref_name(data[:ref]))
+
+ opened_merge_requests.empty? && data[:total_commits_count] > 0 &&
+ !Gitlab::Git.blank_ref?(data[:after])
+ end
+
+ def merge_request_valid?(data)
+ ['opened', 'reopened'].include?(data[:object_attributes][:state]) &&
+ data[:object_attributes][:merge_status] == 'unchecked'
+ end
+end
diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index acbbc9935b6..4dcd16ede3a 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -19,25 +19,20 @@
#
class GitlabCiService < CiService
- API_PREFIX = "api/v1"
-
- prop_accessor :project_url, :token, :enable_ssl_verification
- validates :project_url,
- presence: true,
- format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated?
- validates :token,
- presence: true,
- format: { with: /\A([A-Za-z0-9]+)\z/ }, if: :activated?
+ include Gitlab::Application.routes.url_helpers
after_save :compose_service_hook, if: :activated?
+ after_save :ensure_gitlab_ci_project, if: :activated?
def compose_service_hook
hook = service_hook || build_service_hook
- hook.url = [project_url, "/build", "?token=#{token}"].join("")
- hook.enable_ssl_verification = enable_ssl_verification
hook.save
end
+ def ensure_gitlab_ci_project
+ project.ensure_gitlab_ci_project
+ end
+
def supported_events
%w(push tag_push)
end
@@ -45,81 +40,39 @@ class GitlabCiService < CiService
def execute(data)
return unless supported_events.include?(data[:object_kind])
- sha = data[:checkout_sha]
-
- if sha.present?
- file = ci_yaml_file(sha)
-
- if file && file.data
- data.merge!(ci_yaml_file: file.data)
- end
+ ci_project = project.gitlab_ci_project
+ if ci_project
+ current_user = User.find_by(id: data[:user_id])
+ Ci::CreateCommitService.new.execute(ci_project, current_user, data)
end
-
- service_hook.execute(data)
end
- def commit_status_path(sha, ref)
- URI::encode(project_url + "/refs/#{ref}/commits/#{sha}/status.json?token=#{token}")
+ def token
+ if project.gitlab_ci_project.present?
+ project.gitlab_ci_project.token
+ end
end
- def get_ci_build(sha, ref)
- @ci_builds ||= {}
- @ci_builds[sha] ||= HTTParty.get(commit_status_path(sha, ref), verify: false)
+ def get_ci_commit(sha, ref)
+ Ci::Project.find(project.gitlab_ci_project).commits.find_by_sha!(sha)
end
def commit_status(sha, ref)
- response = get_ci_build(sha, ref)
-
- if response.code == 200 and response["status"]
- response["status"]
- else
- :error
- end
- rescue Errno::ECONNREFUSED
+ get_ci_commit(sha, ref).status
+ rescue ActiveRecord::RecordNotFound
:error
end
- def fork_registration(new_project, private_token)
- params = {
- id: new_project.id,
- name_with_namespace: new_project.name_with_namespace,
- path_with_namespace: new_project.path_with_namespace,
- web_url: new_project.web_url,
- default_branch: new_project.default_branch,
- ssh_url_to_repo: new_project.ssh_url_to_repo
- }
-
- HTTParty.post(
- fork_registration_path,
- body: {
- project_id: project.id,
- project_token: token,
- private_token: private_token,
- data: params },
- verify: false
- )
- end
-
def commit_coverage(sha, ref)
- response = get_ci_build(sha, ref)
-
- if response.code == 200 and response["coverage"]
- response["coverage"]
- end
- rescue Errno::ECONNREFUSED
- nil
+ get_ci_commit(sha, ref).coverage
+ rescue ActiveRecord::RecordNotFound
+ :error
end
def build_page(sha, ref)
- URI::encode(project_url + "/refs/#{ref}/commits/#{sha}")
- end
-
- def builds_path
- project_url + "?ref=" + project.default_branch
- end
-
- def status_img_path
- project_url + "/status.png?ref=" + project.default_branch
+ if project.gitlab_ci_project.present?
+ ci_namespace_project_commit_url(project.namespace, project, sha)
+ end
end
def title
@@ -135,24 +88,6 @@ class GitlabCiService < CiService
end
def fields
- [
- { type: 'text', name: 'token', placeholder: 'GitLab CI project specific token' },
- { type: 'text', name: 'project_url', placeholder: 'http://ci.gitlabhq.com/projects/3' },
- { type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" }
- ]
- end
-
- private
-
- def ci_yaml_file(sha)
- repository.blob_at(sha, '.gitlab-ci.yml')
- end
-
- def fork_registration_path
- project_url.sub(/projects\/\d*/, "#{API_PREFIX}/forks")
- end
-
- def repository
- project.repository
+ []
end
end
diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb
index 0ebc0a3ba1a..9558292fea3 100644
--- a/app/models/project_services/gitlab_issue_tracker_service.rb
+++ b/app/models/project_services/gitlab_issue_tracker_service.rb
@@ -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
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index 7a15a861abc..af2840a57f0 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -85,17 +85,16 @@ class HipchatService < Service
def create_message(data)
object_kind = data[:object_kind]
- message = \
- case object_kind
- when "push", "tag_push"
- create_push_message(data)
- when "issue"
- create_issue_message(data) unless is_update?(data)
- when "merge_request"
- create_merge_request_message(data) unless is_update?(data)
- when "note"
- create_note_message(data)
- end
+ case object_kind
+ when "push", "tag_push"
+ create_push_message(data)
+ when "issue"
+ create_issue_message(data) unless is_update?(data)
+ when "merge_request"
+ create_merge_request_message(data) unless is_update?(data)
+ when "note"
+ create_note_message(data)
+ end
end
def create_push_message(push)
@@ -167,8 +166,6 @@ class HipchatService < Service
obj_attr = data[:object_attributes]
obj_attr = HashWithIndifferentAccess.new(obj_attr)
merge_request_id = obj_attr[:iid]
- source_branch = obj_attr[:source_branch]
- target_branch = obj_attr[:target_branch]
state = obj_attr[:state]
description = obj_attr[:description]
title = obj_attr[:title]
@@ -194,8 +191,6 @@ class HipchatService < Service
data = HashWithIndifferentAccess.new(data)
user_name = data[:user][:name]
- repo_attr = HashWithIndifferentAccess.new(data[:repository])
-
obj_attr = HashWithIndifferentAccess.new(data[:object_attributes])
note = obj_attr[:note]
note_url = obj_attr[:url]
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index bfa8fc7b860..35e30b1cb0b 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -19,7 +19,7 @@
#
class JiraService < IssueTrackerService
- include Rails.application.routes.url_helpers
+ include Gitlab::Application.routes.url_helpers
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index 36d9874edd3..7cd5e892507 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -34,6 +34,12 @@ class SlackService < Service
'slack'
end
+ def help
+ 'This service sends notifications to your Slack channel.<br/>
+ To setup this Service you need to create a new <b>"Incoming webhook"</b> in your Slack integration panel,
+ and enter the Webhook URL below.'
+ end
+
def fields
[
{ type: 'text', name: 'webhook',
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index 56e49af2324..f602a965364 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -135,6 +135,10 @@ class ProjectTeam
!!find_member(user_id)
end
+ def human_max_access(user_id)
+ Gitlab::Access.options.key max_member_access(user_id)
+ end
+
def max_member_access(user_id)
access = []
access << project.project_members.find_by(user_id: user_id).try(:access_field)
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 79b48ebfedf..2c5ab62d22c 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -549,7 +549,7 @@ class Repository
# Run GitLab post receive hook
post_receive_hook = Gitlab::Git::Hook.new('post-receive', path_to_repo)
- status = post_receive_hook.trigger(gl_id, oldrev, newrev, ref)
+ post_receive_hook.trigger(gl_id, oldrev, newrev, ref)
else
# Remove tmp ref and return error to user
rugged.references.delete(tmp_ref)
diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb
index 460ca40be3f..3eed5c16e45 100644
--- a/app/models/sent_notification.rb
+++ b/app/models/sent_notification.rb
@@ -1,3 +1,17 @@
+# == Schema Information
+#
+# Table name: sent_notifications
+#
+# id :integer not null, primary key
+# project_id :integer
+# noteable_id :integer
+# noteable_type :string(255)
+# recipient_id :integer
+# commit_id :string(255)
+# line_code :string(255)
+# reply_key :string(255) not null
+#
+
class SentNotification < ActiveRecord::Base
belongs_to :project
belongs_to :noteable, polymorphic: true
@@ -8,13 +22,20 @@ class SentNotification < ActiveRecord::Base
validates :noteable_id, presence: true, unless: :for_commit?
validates :commit_id, presence: true, if: :for_commit?
+ validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
class << self
+ def reply_key
+ return nil unless Gitlab::IncomingEmail.enabled?
+
+ SecureRandom.hex(16)
+ end
+
def for(reply_key)
find_by(reply_key: reply_key)
end
- def record(noteable, recipient_id, reply_key)
+ def record(noteable, recipient_id, reply_key, params = {})
return unless reply_key
noteable_id = nil
@@ -25,7 +46,7 @@ class SentNotification < ActiveRecord::Base
noteable_id = noteable.id
end
- create(
+ params.reverse_merge!(
project: noteable.project,
noteable_type: noteable.class.name,
noteable_id: noteable_id,
@@ -33,6 +54,14 @@ class SentNotification < ActiveRecord::Base
recipient_id: recipient_id,
reply_key: reply_key
)
+
+ create(params)
+ end
+
+ def record_note(note, recipient_id, reply_key, params = {})
+ params[:line_code] = note.line_code
+
+ record(note.noteable, recipient_id, reply_key, params)
end
end
diff --git a/app/models/service.rb b/app/models/service.rb
index dcef2866c3b..60fcc9d2857 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -135,6 +135,7 @@ class Service < ActiveRecord::Base
buildkite
campfire
custom_issue_tracker
+ drone_ci
emails_on_push
external_wiki
flowdock
diff --git a/app/models/user.rb b/app/models/user.rb
index 48e0e6ed48b..889d2d3b867 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -2,62 +2,59 @@
#
# Table name: users
#
-# id :integer not null, primary key
-# email :string(255) default(""), not null
-# encrypted_password :string(255) default(""), not null
-# reset_password_token :string(255)
-# reset_password_sent_at :datetime
-# remember_created_at :datetime
-# sign_in_count :integer default(0)
-# current_sign_in_at :datetime
-# last_sign_in_at :datetime
-# current_sign_in_ip :string(255)
-# last_sign_in_ip :string(255)
-# created_at :datetime
-# updated_at :datetime
-# name :string(255)
-# admin :boolean default(FALSE), not null
-# projects_limit :integer default(10)
-# skype :string(255) default(""), not null
-# linkedin :string(255) default(""), not null
-# twitter :string(255) default(""), not null
-# authentication_token :string(255)
-# theme_id :integer default(1), not null
-# bio :string(255)
-# failed_attempts :integer default(0)
-# locked_at :datetime
-# username :string(255)
-# can_create_group :boolean default(TRUE), not null
-# can_create_team :boolean default(TRUE), not null
-# state :string(255)
-# color_scheme_id :integer default(1), not null
-# notification_level :integer default(1), not null
-# password_expires_at :datetime
-# created_by_id :integer
-# last_credential_check_at :datetime
-# avatar :string(255)
-# confirmation_token :string(255)
-# confirmed_at :datetime
-# confirmation_sent_at :datetime
-# unconfirmed_email :string(255)
-# hide_no_ssh_key :boolean default(FALSE)
-# website_url :string(255) default(""), not null
-# github_access_token :string(255)
-# gitlab_access_token :string(255)
-# notification_email :string(255)
-# hide_no_password :boolean default(FALSE)
-# password_automatically_set :boolean default(FALSE)
-# bitbucket_access_token :string(255)
-# bitbucket_access_token_secret :string(255)
-# location :string(255)
-# encrypted_otp_secret :string(255)
-# encrypted_otp_secret_iv :string(255)
-# encrypted_otp_secret_salt :string(255)
-# otp_required_for_login :boolean default(FALSE), not null
-# otp_backup_codes :text
-# public_email :string(255) default(""), not null
-# dashboard :integer default(0)
-# project_view :integer default(0)
+# id :integer not null, primary key
+# email :string(255) default(""), not null
+# encrypted_password :string(255) default(""), not null
+# reset_password_token :string(255)
+# reset_password_sent_at :datetime
+# remember_created_at :datetime
+# sign_in_count :integer default(0)
+# current_sign_in_at :datetime
+# last_sign_in_at :datetime
+# current_sign_in_ip :string(255)
+# last_sign_in_ip :string(255)
+# created_at :datetime
+# updated_at :datetime
+# name :string(255)
+# admin :boolean default(FALSE), not null
+# projects_limit :integer default(10)
+# skype :string(255) default(""), not null
+# linkedin :string(255) default(""), not null
+# twitter :string(255) default(""), not null
+# authentication_token :string(255)
+# theme_id :integer default(1), not null
+# bio :string(255)
+# failed_attempts :integer default(0)
+# locked_at :datetime
+# username :string(255)
+# can_create_group :boolean default(TRUE), not null
+# can_create_team :boolean default(TRUE), not null
+# state :string(255)
+# color_scheme_id :integer default(1), not null
+# notification_level :integer default(1), not null
+# password_expires_at :datetime
+# created_by_id :integer
+# last_credential_check_at :datetime
+# avatar :string(255)
+# confirmation_token :string(255)
+# confirmed_at :datetime
+# confirmation_sent_at :datetime
+# unconfirmed_email :string(255)
+# hide_no_ssh_key :boolean default(FALSE)
+# website_url :string(255) default(""), not null
+# notification_email :string(255)
+# hide_no_password :boolean default(FALSE)
+# password_automatically_set :boolean default(FALSE)
+# location :string(255)
+# encrypted_otp_secret :string(255)
+# encrypted_otp_secret_iv :string(255)
+# encrypted_otp_secret_salt :string(255)
+# otp_required_for_login :boolean default(FALSE), not null
+# otp_backup_codes :text
+# public_email :string(255) default(""), not null
+# dashboard :integer default(0)
+# project_view :integer default(0)
+# layout :integer default(0)
#
require 'carrierwave/orm/activerecord'
@@ -133,6 +130,8 @@ class User < ActiveRecord::Base
has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue"
has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy
+ has_one :abuse_report, dependent: :destroy
+ has_many :ci_builds, dependent: :nullify, class_name: 'Ci::Build'
#
@@ -174,9 +173,12 @@ class User < ActiveRecord::Base
after_create :post_create_hook
after_destroy :post_destroy_hook
+ # User's Layout preference
+ enum layout: [:fixed, :fluid]
+
# User's Dashboard preference
# Note: When adding an option, it MUST go on the end of the array.
- enum dashboard: [:projects, :stars]
+ enum dashboard: [:projects, :stars, :project_activity, :starred_project_activity]
# User's Project preference
# Note: When adding an option, it MUST go on the end of the array.
@@ -331,6 +333,10 @@ class User < ActiveRecord::Base
@reset_token
end
+ def recently_sent_password_reset?
+ reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
+ end
+
def disable_two_factor!
update_attributes(
two_factor_enabled: false,
@@ -704,13 +710,7 @@ class User < ActiveRecord::Base
end
def manageable_namespaces
- @manageable_namespaces ||=
- begin
- namespaces = []
- namespaces << namespace
- namespaces += owned_groups
- namespaces += masters_groups
- end
+ @manageable_namespaces ||= [namespace] + owned_groups + masters_groups
end
def namespaces
@@ -757,4 +757,13 @@ class User < ActiveRecord::Base
def can_be_removed?
!solo_owned_groups.present?
end
+
+ def ci_authorized_projects
+ @ci_authorized_projects ||= Ci::Project.where(gitlab_id: authorized_projects)
+ end
+
+ def ci_authorized_runners
+ Ci::Runner.specific.includes(:runner_projects).
+ where(ci_runner_projects: { project_id: ci_authorized_projects } )
+ end
end
diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb
new file mode 100644
index 00000000000..c420f3268fd
--- /dev/null
+++ b/app/services/ci/create_builds_service.rb
@@ -0,0 +1,27 @@
+module Ci
+ class CreateBuildsService
+ def execute(commit, stage, ref, tag, user, trigger_request)
+ builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag)
+
+ builds_attrs.map do |build_attrs|
+ # don't create the same build twice
+ unless commit.builds.find_by(ref: ref, tag: tag, trigger_request: trigger_request, name: build_attrs[:name])
+ build_attrs.slice!(:name,
+ :commands,
+ :tag_list,
+ :options,
+ :allow_failure,
+ :stage,
+ :stage_idx)
+
+ build_attrs.merge!(ref: ref,
+ tag: tag,
+ trigger_request: trigger_request,
+ user: user)
+
+ commit.builds.create!(build_attrs)
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/ci/create_commit_service.rb b/app/services/ci/create_commit_service.rb
new file mode 100644
index 00000000000..fc1ae5774d5
--- /dev/null
+++ b/app/services/ci/create_commit_service.rb
@@ -0,0 +1,26 @@
+module Ci
+ class CreateCommitService
+ def execute(project, user, params)
+ sha = params[:checkout_sha] || params[:after]
+ origin_ref = params[:ref]
+
+ unless origin_ref && sha.present?
+ return false
+ end
+
+ ref = origin_ref.gsub(/\Arefs\/(tags|heads)\//, '')
+
+ # Skip branch removal
+ if sha == Ci::Git::BLANK_SHA
+ return false
+ end
+
+ tag = origin_ref.start_with?('refs/tags/')
+ commit = project.gl_project.ensure_ci_commit(sha)
+ commit.update_committed!
+ commit.create_builds(ref, tag, user)
+
+ commit
+ end
+ end
+end
diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb
new file mode 100644
index 00000000000..4b86cb0a1f5
--- /dev/null
+++ b/app/services/ci/create_trigger_request_service.rb
@@ -0,0 +1,22 @@
+module Ci
+ class CreateTriggerRequestService
+ def execute(project, trigger, ref, variables = nil)
+ commit = project.gl_project.commit(ref)
+ return unless commit
+
+ # check if ref is tag
+ tag = project.gl_project.repository.find_tag(ref).present?
+
+ ci_commit = project.gl_project.ensure_ci_commit(commit.sha)
+
+ trigger_request = trigger.trigger_requests.create!(
+ variables: variables,
+ commit: ci_commit,
+ )
+
+ if ci_commit.create_builds(ref, tag, nil, trigger_request)
+ trigger_request
+ end
+ end
+ end
+end
diff --git a/app/services/ci/event_service.rb b/app/services/ci/event_service.rb
new file mode 100644
index 00000000000..3f4e02dd26c
--- /dev/null
+++ b/app/services/ci/event_service.rb
@@ -0,0 +1,31 @@
+module Ci
+ class EventService
+ def remove_project(user, project)
+ create(
+ description: "Project \"#{project.name}\" has been removed by #{user.username}",
+ user_id: user.id,
+ is_admin: true
+ )
+ end
+
+ def create_project(user, project)
+ create(
+ description: "Project \"#{project.name}\" has been created by #{user.username}",
+ user_id: user.id,
+ is_admin: true
+ )
+ end
+
+ def change_project_settings(user, project)
+ create(
+ project_id: project.id,
+ user_id: user.id,
+ description: "User \"#{user.username}\" updated projects settings"
+ )
+ end
+
+ def create(*args)
+ Ci::Event.create!(*args)
+ end
+ end
+end
diff --git a/app/services/ci/image_for_build_service.rb b/app/services/ci/image_for_build_service.rb
new file mode 100644
index 00000000000..b95835ba093
--- /dev/null
+++ b/app/services/ci/image_for_build_service.rb
@@ -0,0 +1,31 @@
+module Ci
+ class ImageForBuildService
+ def execute(project, params)
+ image_name =
+ if params[:sha]
+ commit = project.commits.find_by(sha: params[:sha])
+ image_for_commit(commit)
+ elsif params[:ref]
+ commit = project.last_commit_for_ref(params[:ref])
+ image_for_commit(commit)
+ else
+ 'build-unknown.svg'
+ end
+
+ image_path = Rails.root.join('public/ci', image_name)
+
+ OpenStruct.new(
+ path: image_path,
+ name: image_name
+ )
+ end
+
+ private
+
+ def image_for_commit(commit)
+ return 'build-unknown.svg' unless commit
+
+ 'build-' + commit.status + ".svg"
+ end
+ end
+end
diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb
new file mode 100644
index 00000000000..71b61bbe389
--- /dev/null
+++ b/app/services/ci/register_build_service.rb
@@ -0,0 +1,40 @@
+module Ci
+ # This class responsible for assigning
+ # proper pending build to runner on runner API request
+ class RegisterBuildService
+ def execute(current_runner)
+ builds = Ci::Build.pending.unstarted
+
+ builds =
+ if current_runner.shared?
+ # don't run projects which have not enables shared runners
+ builds.joins(commit: { gl_project: :gitlab_ci_project }).where(ci_projects: { shared_runners_enabled: true })
+ else
+ # do run projects which are only assigned to this runner
+ builds.joins(:commit).where(ci_commits: { gl_project_id: current_runner.gl_projects_ids })
+ end
+
+ builds = builds.order('created_at ASC')
+
+ build = builds.find do |build|
+ (build.tag_list - current_runner.tag_list).empty?
+ end
+
+
+ if build
+ # In case when 2 runners try to assign the same build, second runner will be declined
+ # with StateMachine::InvalidTransition in run! method.
+ build.with_lock do
+ build.runner_id = current_runner.id
+ build.save!
+ build.run!
+ end
+ end
+
+ build
+
+ rescue StateMachine::InvalidTransition
+ nil
+ end
+ end
+end
diff --git a/app/services/ci/test_hook_service.rb b/app/services/ci/test_hook_service.rb
new file mode 100644
index 00000000000..3a17596aaeb
--- /dev/null
+++ b/app/services/ci/test_hook_service.rb
@@ -0,0 +1,7 @@
+module Ci
+ class TestHookService
+ def execute(hook, current_user)
+ Ci::WebHookService.new.build_end(hook.project.commits.last.last_build)
+ end
+ end
+end
diff --git a/app/services/ci/web_hook_service.rb b/app/services/ci/web_hook_service.rb
new file mode 100644
index 00000000000..92e6df442b4
--- /dev/null
+++ b/app/services/ci/web_hook_service.rb
@@ -0,0 +1,35 @@
+module Ci
+ class WebHookService
+ def build_end(build)
+ execute_hooks(build.project, build_data(build))
+ end
+
+ def execute_hooks(project, data)
+ project.web_hooks.each do |web_hook|
+ async_execute_hook(web_hook, data)
+ end
+ end
+
+ def async_execute_hook(hook, data)
+ Sidekiq::Client.enqueue(Ci::WebHookWorker, hook.id, data)
+ end
+
+ def build_data(build)
+ project = build.project
+ data = {}
+ data.merge!({
+ build_id: build.id,
+ build_name: build.name,
+ build_status: build.status,
+ build_started_at: build.started_at,
+ build_finished_at: build.finished_at,
+ project_id: project.id,
+ project_name: project.name,
+ gitlab_url: project.gitlab_url,
+ ref: build.ref,
+ before_sha: build.before_sha,
+ sha: build.sha,
+ })
+ end
+ end
+end
diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb
index 70f642baaaa..bfe6a3dc4be 100644
--- a/app/services/compare_service.rb
+++ b/app/services/compare_service.rb
@@ -4,7 +4,10 @@ require 'securerandom'
# and return Gitlab::CompareResult object that responds to commits and diffs
class CompareService
def execute(source_project, source_branch, target_project, target_branch)
- source_sha = source_project.commit(source_branch).sha
+ source_commit = source_project.commit(source_branch)
+ return unless source_commit
+
+ source_sha = source_commit.sha
# If compare with other project we need to fetch ref first
unless target_project == source_project
diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb
index 103d6b0a08b..07fc77001a5 100644
--- a/app/services/event_create_service.rb
+++ b/app/services/event_create_service.rb
@@ -46,6 +46,10 @@ class EventCreateService
create_record_event(milestone, current_user, Event::REOPENED)
end
+ def destroy_milestone(milestone, current_user)
+ create_record_event(milestone, current_user, Event::DESTROYED)
+ end
+
def leave_note(note, current_user)
create_record_event(note, current_user, Event::COMMENTED)
end
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index 7aecee217d8..008833eed80 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -21,7 +21,7 @@ module Files
create_target_branch
end
- if sha = commit
+ if commit
success
else
error("Something went wrong. Your changes were not committed")
diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb
index 91d715b2d63..ffbb5993279 100644
--- a/app/services/files/create_service.rb
+++ b/app/services/files/create_service.rb
@@ -19,10 +19,12 @@ module Files
end
unless project.empty_repo?
+ @file_path.slice!(0) if @file_path.start_with?('/')
+
blob = repository.blob_at_branch(@current_branch, @file_path)
if blob
- raise_error("Your changes could not be committed, because file with such name exists")
+ raise_error("Your changes could not be committed because a file with the same name already exists")
end
end
end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 0a73244774a..f9a8265d2d4 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -55,6 +55,12 @@ class GitPushService
@push_data = build_push_data(oldrev, newrev, ref)
+ # If CI was disabled but .gitlab-ci.yml file was pushed
+ # we enable CI automatically
+ if !project.gitlab_ci? && gitlab_ci_yaml?(newrev)
+ project.enable_ci
+ end
+
EventCreateService.new.push(project, user, @push_data)
project.execute_hooks(@push_data.dup, :push_hooks)
project.execute_services(@push_data.dup, :push_hooks)
@@ -143,4 +149,10 @@ class GitPushService
def commit_user(commit)
commit.author || user
end
+
+ def gitlab_ci_yaml?(sha)
+ @project.repository.blob_at(sha, '.gitlab-ci.yml')
+ rescue Rugged::ReferenceError
+ nil
+ end
end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 98a67c0bc99..fcc0f2a6a8d 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -38,6 +38,10 @@ module MergeRequests
}
repository.merge(current_user, merge_request.source_sha, merge_request.target_branch, options)
+ rescue Exception => e
+ merge_request.update(merge_error: "Something went wrong during merge")
+ Rails.logger.error(e.message)
+ return false
end
def after_merge
diff --git a/app/services/milestones/destroy_service.rb b/app/services/milestones/destroy_service.rb
new file mode 100644
index 00000000000..2414966505b
--- /dev/null
+++ b/app/services/milestones/destroy_service.rb
@@ -0,0 +1,27 @@
+module Milestones
+ class DestroyService < Milestones::BaseService
+ def execute(milestone)
+
+ Milestone.transaction do
+ update_params = { milestone: nil }
+
+ milestone.issues.each do |issue|
+ Issues::UpdateService.new(project, current_user, update_params).execute(issue)
+ end
+
+ milestone.merge_requests.each do |merge_request|
+ MergeRequests::UpdateService.new(project, current_user, update_params).execute(merge_request)
+ end
+
+ event_service.destroy_milestone(milestone, current_user)
+
+ Event.for_milestone_id(milestone.id).each do |event|
+ event.target_id = nil
+ event.save
+ end
+
+ milestone.destroy
+ end
+ end
+ end
+end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index e294b23bc23..a6b22348650 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -183,12 +183,12 @@ class NotificationService
mailer.group_access_granted_email(group_member.id)
end
- def project_was_moved(project)
+ def project_was_moved(project, old_path_with_namespace)
recipients = project.team.members
recipients = reject_muted_users(recipients, project)
recipients.each do |recipient|
- mailer.project_was_moved_email(project.id, recipient.id)
+ mailer.project_was_moved_email(project.id, recipient.id, old_path_with_namespace)
end
end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index b35aed005da..faf1ee008e7 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -55,16 +55,14 @@ module Projects
@project.save
if @project.persisted? && !@project.import?
- unless @project.create_repository
- raise 'Failed to create repository'
- end
+ raise 'Failed to create repository' unless @project.create_repository
end
end
after_create_actions if @project.persisted?
@project
- rescue => ex
+ rescue
@project.errors.add(:base, "Can't save project. Please try again later")
@project
end
@@ -87,6 +85,8 @@ module Projects
@project.build_missing_services
+ @project.create_labels
+
event_service.create_project(@project, current_user)
system_hook_service.execute_hooks_for(@project, :create)
diff --git a/app/services/projects/download_service.rb b/app/services/projects/download_service.rb
new file mode 100644
index 00000000000..99f22293d0d
--- /dev/null
+++ b/app/services/projects/download_service.rb
@@ -0,0 +1,43 @@
+module Projects
+ class DownloadService < BaseService
+
+ WHITELIST = [
+ /^[^.]+\.fogbugz.com$/
+ ]
+
+ def initialize(project, url)
+ @project, @url = project, url
+ end
+
+ def execute
+ return nil unless valid_url?(@url)
+
+ uploader = FileUploader.new(@project)
+ uploader.download!(@url)
+ uploader.store!
+
+ filename = uploader.image? ? uploader.file.basename : uploader.file.filename
+
+ {
+ 'alt' => filename,
+ 'url' => uploader.secure_url,
+ 'is_image' => uploader.image?
+ }
+ end
+
+ private
+
+ def valid_url?(url)
+ url && http?(url) && valid_domain?(url)
+ end
+
+ def http?(url)
+ url =~ /\A#{URI::regexp(['http', 'https'])}\z/
+ end
+
+ def valid_domain?(url)
+ host = URI.parse(url).host
+ WHITELIST.any? { |entry| entry === host }
+ end
+ end
+end
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index 50f208b11d1..46374a3909a 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -18,7 +18,13 @@ module Projects
if new_project.persisted?
if @project.gitlab_ci?
- ForkRegistrationWorker.perform_async(@project.id, new_project.id, @current_user.private_token)
+ new_project.enable_ci
+
+ settings = @project.gitlab_ci_project.attributes.select do |attr_name, value|
+ ["public", "shared_runners_enabled", "allow_git_fetch"].include? attr_name
+ end
+
+ new_project.gitlab_ci_project.update(settings)
end
end
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 550ed6897dd..c327c244f0d 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -38,7 +38,7 @@ module Projects
project.save!
# Notifications
- project.send_move_instructions
+ project.send_move_instructions(old_path)
# Move main repository
unless gitlab_shell.mv_repository(old_path, new_path)
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 330b8bbf9d2..a36ae0b766c 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -118,6 +118,11 @@
.col-sm-10
= f.text_area :sign_in_text, class: 'form-control', rows: 4
.help-block Markdown enabled
+ .form-group
+ = f.label :help_page_text, class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_area :help_page_text, class: 'form-control', rows: 4
+ .help-block Markdown enabled
.form-actions
= f.submit 'Save', class: 'btn btn-primary'
diff --git a/app/views/admin/applications/index.html.haml b/app/views/admin/applications/index.html.haml
index fc921a966f3..f8cd98f0ec4 100644
--- a/app/views/admin/applications/index.html.haml
+++ b/app/views/admin/applications/index.html.haml
@@ -2,7 +2,7 @@
%h3.page-title
System OAuth applications
%p.light
- System OAuth application does not belong to certain user and can be managed only by admins
+ System OAuth applications don't belong to any user and can only be managed by admins
%hr
%p= link_to 'New Application', new_admin_application_path, class: 'btn btn-success'
%table.table.table-striped
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 54191aadda6..8657d2c71fe 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -58,7 +58,7 @@
%p
Reply by email
%span.light.pull-right
- = boolean_to_icon Gitlab::ReplyByEmail.enabled?
+ = boolean_to_icon Gitlab::IncomingEmail.enabled?
.col-md-4
%h4
Components
diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml
new file mode 100644
index 00000000000..ad58a3837f6
--- /dev/null
+++ b/app/views/admin/labels/_form.html.haml
@@ -0,0 +1,35 @@
+= form_for [:admin, @label], html: { class: 'form-horizontal label-form js-requires-input' } do |f|
+ -if @label.errors.any?
+ .row
+ .col-sm-offset-2.col-sm-10
+ .alert.alert-danger
+ - @label.errors.full_messages.each do |msg|
+ %span= msg
+ %br
+
+ .form-group
+ = f.label :title, class: 'control-label'
+ .col-sm-10
+ = f.text_field :title, class: "form-control", required: true
+ .form-group
+ = f.label :color, "Background Color", class: 'control-label'
+ .col-sm-10
+ .input-group
+ .input-group-addon.label-color-preview &nbsp;
+ = f.color_field :color, class: "form-control"
+ .help-block
+ Choose any color.
+ %br
+ Or you can choose one of suggested colors below
+
+ .suggest-colors
+ - suggested_colors.each do |color|
+ = link_to '#', style: "background-color: #{color}", data: { color: color } do
+ &nbsp;
+
+ .form-actions
+ = f.submit 'Save', class: 'btn btn-save js-save-button'
+ = link_to "Cancel", admin_labels_path, class: 'btn btn-cancel'
+
+:coffeescript
+ new Labels
diff --git a/app/views/admin/labels/_label.html.haml b/app/views/admin/labels/_label.html.haml
new file mode 100644
index 00000000000..596e06243dd
--- /dev/null
+++ b/app/views/admin/labels/_label.html.haml
@@ -0,0 +1,5 @@
+%li{id: dom_id(label)}
+ = render_colored_label(label)
+ .pull-right
+ = link_to 'Edit', edit_admin_label_path(label), class: 'btn btn-sm'
+ = link_to 'Remove', admin_label_path(label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
diff --git a/app/views/admin/labels/destroy.js.haml b/app/views/admin/labels/destroy.js.haml
new file mode 100644
index 00000000000..9d51762890f
--- /dev/null
+++ b/app/views/admin/labels/destroy.js.haml
@@ -0,0 +1,2 @@
+- if @labels.size == 0
+ $('.labels').load(document.URL + ' .light-well').hide().fadeIn(1000)
diff --git a/app/views/admin/labels/edit.html.haml b/app/views/admin/labels/edit.html.haml
new file mode 100644
index 00000000000..45c62a76259
--- /dev/null
+++ b/app/views/admin/labels/edit.html.haml
@@ -0,0 +1,9 @@
+- page_title "Edit", @label.name, "Labels"
+%h3
+ Edit label
+ %span.light #{@label.name}
+.back-link
+ = link_to admin_labels_path do
+ &larr; To labels list
+%hr
+= render 'form'
diff --git a/app/views/admin/labels/index.html.haml b/app/views/admin/labels/index.html.haml
new file mode 100644
index 00000000000..d67454c03e7
--- /dev/null
+++ b/app/views/admin/labels/index.html.haml
@@ -0,0 +1,16 @@
+- page_title "Labels"
+= link_to new_admin_label_path, class: "pull-right btn btn-new" do
+ New label
+%h3.page-title
+ Labels
+%hr
+
+.labels
+ - if @labels.present?
+ %ul.bordered-list.manage-labels-list
+ = render @labels
+ = paginate @labels, theme: 'gitlab'
+ - else
+ .light-well
+ .nothing-here-block There are no labels yet
+
diff --git a/app/views/admin/labels/new.html.haml b/app/views/admin/labels/new.html.haml
new file mode 100644
index 00000000000..8d298ad20f7
--- /dev/null
+++ b/app/views/admin/labels/new.html.haml
@@ -0,0 +1,7 @@
+- page_title "New Label"
+%h3 New label
+.back-link
+ = link_to admin_labels_path do
+ &larr; To labels list
+%hr
+= render 'form'
diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml
index 9d5e934c8ba..4245d0f1eda 100644
--- a/app/views/admin/users/_head.html.haml
+++ b/app/views/admin/users/_head.html.haml
@@ -6,6 +6,8 @@
%span.cred (Admin)
.pull-right
+ - unless @user == current_user
+ = link_to 'Log in as this user', login_as_admin_user_path(@user), method: :post, class: "btn btn-grouped btn-info"
= link_to edit_admin_user_path(@user), class: "btn btn-grouped" do
%i.fa.fa-pencil-square-o
Edit
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index 5e40d95d1c5..e3698ac1c46 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -97,5 +97,5 @@
- if user.access_locked?
= link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: "btn btn-xs btn-success", data: { confirm: 'Are you sure?' }
- if user.can_be_removed?
- = link_to 'Destroy', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All tickets linked to this user will also be removed! Maybe block the user instead? Are you sure?" }, method: :delete, class: "btn btn-xs btn-remove"
+ = link_to 'Destroy', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and groups linked to this user will also be removed! Maybe block the user instead? Are you sure?" }, method: :delete, class: "btn btn-xs btn-remove"
= paginate @users, theme: "gitlab"
diff --git a/app/views/ci/admin/application_settings/_form.html.haml b/app/views/ci/admin/application_settings/_form.html.haml
new file mode 100644
index 00000000000..634c9daa477
--- /dev/null
+++ b/app/views/ci/admin/application_settings/_form.html.haml
@@ -0,0 +1,24 @@
+= form_for @application_setting, url: ci_admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+ - if @application_setting.errors.any?
+ #error_explanation
+ .alert.alert-danger
+ - @application_setting.errors.full_messages.each do |msg|
+ %p= msg
+
+ %fieldset
+ %legend Default Project Settings
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :all_broken_builds do
+ = f.check_box :all_broken_builds
+ Send emails only on broken builds
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :add_pusher do
+ = f.check_box :add_pusher
+ Add pusher to recipients list
+
+ .form-actions
+ = f.submit 'Save', class: 'btn btn-primary'
diff --git a/app/views/ci/admin/application_settings/show.html.haml b/app/views/ci/admin/application_settings/show.html.haml
new file mode 100644
index 00000000000..7ef0aa89ed6
--- /dev/null
+++ b/app/views/ci/admin/application_settings/show.html.haml
@@ -0,0 +1,3 @@
+%h3.page-title Settings
+%hr
+= render 'form'
diff --git a/app/views/ci/admin/builds/_build.html.haml b/app/views/ci/admin/builds/_build.html.haml
new file mode 100644
index 00000000000..2df58713214
--- /dev/null
+++ b/app/views/ci/admin/builds/_build.html.haml
@@ -0,0 +1,34 @@
+- gl_project = build.project.gl_project
+- if build.commit && build.project
+ %tr.build
+ %td.build-link
+ = link_to namespace_project_build_path(gl_project.namespace, gl_project, build) do
+ %strong #{build.id}
+
+ %td.status
+ = ci_status_with_icon(build.status)
+
+ %td.commit-link
+ = link_to ci_status_path(build.commit) do
+ %strong #{build.commit.short_sha}
+
+ %td.runner
+ - if build.runner
+ = link_to build.runner.id, ci_admin_runner_path(build.runner)
+
+ %td.build-project
+ = truncate build.project.name, length: 30
+
+ %td.build-message
+ %span= truncate(build.commit.git_commit_message, length: 30)
+
+ %td.build-branch
+ %span= truncate(build.ref, length: 25)
+
+ %td.duration
+ - if build.duration
+ #{duration_in_words(build.finished_at, build.started_at)}
+
+ %td.timestamp
+ - if build.finished_at
+ %span #{time_ago_in_words build.finished_at} ago
diff --git a/app/views/ci/admin/builds/index.html.haml b/app/views/ci/admin/builds/index.html.haml
new file mode 100644
index 00000000000..d23119162cc
--- /dev/null
+++ b/app/views/ci/admin/builds/index.html.haml
@@ -0,0 +1,28 @@
+%ul.nav.nav-tabs.append-bottom-20
+ %li{class: ("active" if @scope.nil?)}
+ = link_to 'All builds', ci_admin_builds_path
+
+ %li{class: ("active" if @scope == "pending")}
+ = link_to "Pending", ci_admin_builds_path(scope: :pending)
+
+ %li{class: ("active" if @scope == "running")}
+ = link_to "Running", ci_admin_builds_path(scope: :running)
+
+
+%table.builds
+ %thead
+ %tr
+ %th Build
+ %th Status
+ %th Commit
+ %th Runner
+ %th Project
+ %th Message
+ %th Branch
+ %th Duration
+ %th Finished at
+
+ - @builds.each do |build|
+ = render "ci/admin/builds/build", build: build
+
+= paginate @builds
diff --git a/app/views/ci/admin/events/index.html.haml b/app/views/ci/admin/events/index.html.haml
new file mode 100644
index 00000000000..f9ab0994304
--- /dev/null
+++ b/app/views/ci/admin/events/index.html.haml
@@ -0,0 +1,17 @@
+%table.table
+ %thead
+ %tr
+ %th User ID
+ %th Description
+ %th When
+ - @events.each do |event|
+ %tr
+ %td
+ = event.user_id
+ %td
+ = event.description
+ %td.light
+ = time_ago_in_words event.updated_at
+ ago
+
+= paginate @events \ No newline at end of file
diff --git a/app/views/ci/admin/projects/_project.html.haml b/app/views/ci/admin/projects/_project.html.haml
new file mode 100644
index 00000000000..a342d6e1cf0
--- /dev/null
+++ b/app/views/ci/admin/projects/_project.html.haml
@@ -0,0 +1,29 @@
+- last_commit = project.commits.last
+%tr
+ %td
+ = project.id
+ %td
+ = link_to [:ci, project] do
+ %strong= project.name
+ %td
+ - if last_commit
+ = ci_status_with_icon(last_commit.status)
+ - if project.last_commit_date
+ &middot;
+ = time_ago_in_words project.last_commit_date
+ ago
+ - else
+ No builds yet
+ %td
+ - if project.public
+ %i.fa.fa-globe
+ Public
+ - else
+ %i.fa.fa-lock
+ Private
+ %td
+ = project.commits.count
+ %td
+ = link_to [:ci, :admin, project], method: :delete, class: 'btn btn-danger btn-sm' do
+ %i.fa.fa-remove
+ Remove
diff --git a/app/views/ci/admin/projects/index.html.haml b/app/views/ci/admin/projects/index.html.haml
new file mode 100644
index 00000000000..dc7b041473b
--- /dev/null
+++ b/app/views/ci/admin/projects/index.html.haml
@@ -0,0 +1,15 @@
+%table.table
+ %thead
+ %tr
+ %th ID
+ %th Name
+ %th Last build
+ %th Access
+ %th Builds
+ %th
+
+ - @projects.each do |project|
+ = render "ci/admin/projects/project", project: project
+
+= paginate @projects
+
diff --git a/app/views/ci/admin/runner_projects/index.html.haml b/app/views/ci/admin/runner_projects/index.html.haml
new file mode 100644
index 00000000000..f049b4f4c4e
--- /dev/null
+++ b/app/views/ci/admin/runner_projects/index.html.haml
@@ -0,0 +1,57 @@
+%p.lead
+ To register new runner visit #{link_to 'this page ', ci_runners_path}
+
+.row
+ .col-md-8
+ %h5 Activated:
+ %table.table
+ %tr
+ %th Runner ID
+ %th Runner Description
+ %th Last build
+ %th Builds Stats
+ %th Registered
+ %th
+
+ - @runner_projects.each do |runner_project|
+ - runner = runner_project.runner
+ - builds = runner.builds.where(project_id: @project.id)
+ %tr
+ %td
+ %span.badge.badge-info= runner.id
+ %td
+ = runner.display_name
+ %td
+ - last_build = builds.last
+ - if last_build
+ = link_to last_build.short_sha, [last_build.project, last_build]
+ - else
+ unknown
+ %td
+ %span.badge.badge-success
+ #{builds.success.count}
+ %span /
+ %span.badge.badge-important
+ #{builds.failed.count}
+ %td
+ #{time_ago_in_words(runner_project.created_at)} ago
+ %td
+ = link_to 'Disable', [:ci, @project, runner_project], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm right'
+ .col-md-4
+ %h5 Available
+ %table.table
+ %tr
+ %th ID
+ %th Token
+ %th
+
+ - (Ci::Runner.all - @project.runners).each do |runner|
+ %tr
+ %td
+ = runner.id
+ %td
+ = runner.token
+ %td
+ = form_for [:ci, @project, @runner_project] do |f|
+ = f.hidden_field :runner_id, value: runner.id
+ = f.submit 'Add', class: 'btn btn-sm'
diff --git a/app/views/ci/admin/runners/_runner.html.haml b/app/views/ci/admin/runners/_runner.html.haml
new file mode 100644
index 00000000000..701782d26bb
--- /dev/null
+++ b/app/views/ci/admin/runners/_runner.html.haml
@@ -0,0 +1,48 @@
+%tr{id: dom_id(runner)}
+ %td
+ - if runner.shared?
+ %span.label.label-success shared
+ - else
+ %span.label.label-info specific
+ - unless runner.active?
+ %span.label.label-danger paused
+
+ %td
+ = link_to ci_admin_runner_path(runner) do
+ = runner.short_sha
+ %td
+ .runner-description
+ = runner.description
+ %span (#{link_to 'edit', '#', class: 'edit-runner-link'})
+ .runner-description-form.hide
+ = form_for [:ci, :admin, runner], remote: true, html: { class: 'form-inline' } do |f|
+ .form-group
+ = f.text_field :description, class: 'form-control'
+ = f.submit 'Save', class: 'btn'
+ %span (#{link_to 'cancel', '#', class: 'cancel'})
+ %td
+ - if runner.shared?
+ \-
+ - else
+ = runner.projects.count(:all)
+ %td
+ #{runner.builds.count(:all)}
+ %td
+ - runner.tag_list.each do |tag|
+ %span.label.label-primary
+ = tag
+ %td
+ - if runner.contacted_at
+ #{time_ago_in_words(runner.contacted_at)} ago
+ - else
+ Never
+ %td
+ .pull-right
+ = link_to 'Edit', ci_admin_runner_path(runner), class: 'btn btn-sm'
+ &nbsp;
+ - if runner.active?
+ = link_to 'Pause', [:pause, :ci, :admin, runner], data: { confirm: "Are you sure?" }, method: :get, class: 'btn btn-danger btn-sm'
+ - else
+ = link_to 'Resume', [:resume, :ci, :admin, runner], method: :get, class: 'btn btn-success btn-sm'
+ = link_to 'Remove', [:ci, :admin, runner], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
+
diff --git a/app/views/ci/admin/runners/index.html.haml b/app/views/ci/admin/runners/index.html.haml
new file mode 100644
index 00000000000..b9d6703ff41
--- /dev/null
+++ b/app/views/ci/admin/runners/index.html.haml
@@ -0,0 +1,52 @@
+%p.lead
+ %span To register new runner you should enter the following registration token. With this token the runner will request a unique runner token and use that for future communication.
+ %code #{GitlabCi::REGISTRATION_TOKEN}
+
+.bs-callout
+ %p
+ A 'runner' is a process which runs a build.
+ You can setup as many runners as you need.
+ %br
+ Runners can be placed on separate users, servers, and even on your local machine.
+ %br
+
+ %div
+ %span Each runner can be in one of the following states:
+ %ul
+ %li
+ %span.label.label-success shared
+ \- run builds from all unassigned projects
+ %li
+ %span.label.label-info specific
+ \- run builds from assigned projects
+ %li
+ %span.label.label-danger paused
+ \- runner will not receive any new build
+
+.append-bottom-20.clearfix
+ .pull-left
+ = form_tag ci_admin_runners_path, id: 'runners-search', class: 'form-inline', method: :get do
+ .form-group
+ = search_field_tag :search, params[:search], class: 'form-control', placeholder: 'Runner description or token'
+ = submit_tag 'Search', class: 'btn'
+
+ .pull-right.light
+ Runners with last contact less than a minute ago: #{@active_runners_cnt}
+
+%br
+
+%table.table
+ %thead
+ %tr
+ %th Type
+ %th Runner token
+ %th Description
+ %th Projects
+ %th Builds
+ %th Tags
+ %th Last contact
+ %th
+
+ - @runners.each do |runner|
+ = render "ci/admin/runners/runner", runner: runner
+= paginate @runners
diff --git a/app/views/ci/admin/runners/show.html.haml b/app/views/ci/admin/runners/show.html.haml
new file mode 100644
index 00000000000..5bb442cbf92
--- /dev/null
+++ b/app/views/ci/admin/runners/show.html.haml
@@ -0,0 +1,124 @@
+= content_for :title do
+ %h3.project-title
+ Runner ##{@runner.id}
+ .pull-right
+ - if @runner.shared?
+ %span.runner-state.runner-state-shared
+ Shared
+ - else
+ %span.runner-state.runner-state-specific
+ Specific
+
+
+
+- if @runner.shared?
+ .bs-callout.bs-callout-success
+ %h4 This runner will process build from ALL UNASSIGNED projects
+ %p
+ If you want runners to build only specific projects, enable them in the table below.
+ Keep in mind that this is a one way transition.
+- else
+ .bs-callout.bs-callout-info
+ %h4 This runner will process build only from ASSIGNED projects
+ %p You can't make this a shared runner.
+%hr
+= form_for @runner, url: ci_admin_runner_path(@runner), html: { class: 'form-horizontal' } do |f|
+ .form-group
+ = label_tag :token, class: 'control-label' do
+ Token
+ .col-sm-10
+ = f.text_field :token, class: 'form-control', readonly: true
+ .form-group
+ = label_tag :description, class: 'control-label' do
+ Description
+ .col-sm-10
+ = f.text_field :description, class: 'form-control'
+ .form-group
+ = label_tag :tag_list, class: 'control-label' do
+ Tags
+ .col-sm-10
+ = f.text_field :tag_list, class: 'form-control'
+ .help-block You can setup builds to only use runners with specific tags
+ .form-actions
+ = f.submit 'Save', class: 'btn btn-save'
+
+.row
+ .col-md-6
+ %h4 Restrict projects for this runner
+ - if @runner.projects.any?
+ %table.table
+ %thead
+ %tr
+ %th Assigned projects
+ %th
+ - @runner.runner_projects.each do |runner_project|
+ - project = runner_project.project
+ %tr.alert-info
+ %td
+ %strong
+ = project.name
+ %td
+ .pull-right
+ = link_to 'Disable', [:ci, :admin, project, runner_project], method: :delete, class: 'btn btn-danger btn-xs'
+
+ %table.table
+ %thead
+ %tr
+ %th Project
+ %th
+ .pull-right
+ = link_to 'Assign to all', assign_all_ci_admin_runner_path(@runner),
+ class: 'btn btn-sm assign-all-runner',
+ title: 'Assign runner to all projects',
+ method: :put
+
+ %tr
+ %td
+ = form_tag ci_admin_runner_path(@runner), id: 'runner-projects-search', class: 'form-inline', method: :get do
+ .form-group
+ = search_field_tag :search, params[:search], class: 'form-control'
+ = submit_tag 'Search', class: 'btn'
+
+ %td
+ - @projects.each do |project|
+ %tr
+ %td
+ = project.name
+ %td
+ .pull-right
+ = form_for [:ci, :admin, project, project.runner_projects.new] do |f|
+ = f.hidden_field :runner_id, value: @runner.id
+ = f.submit 'Enable', class: 'btn btn-xs'
+ = paginate @projects
+
+ .col-md-6
+ %h4 Recent builds served by this runner
+ %table.builds.runner-builds
+ %thead
+ %tr
+ %th Build ID
+ %th Status
+ %th Project
+ %th Commit
+ %th Finished at
+
+ - @builds.each do |build|
+ %tr.build
+ %td.id
+ - gl_project = build.project.gl_project
+ = link_to namespace_project_build_path(gl_project.namespace, gl_project, build) do
+ = build.id
+
+ %td.status
+ = ci_status_with_icon(build.status)
+
+ %td.status
+ = build.project.name
+
+ %td.build-link
+ = link_to ci_status_path(build.commit) do
+ %strong #{build.commit.short_sha}
+
+ %td.timestamp
+ - if build.finished_at
+ %span #{time_ago_in_words build.finished_at} ago
diff --git a/app/views/ci/admin/runners/update.js.haml b/app/views/ci/admin/runners/update.js.haml
new file mode 100644
index 00000000000..2b7d3067e20
--- /dev/null
+++ b/app/views/ci/admin/runners/update.js.haml
@@ -0,0 +1,2 @@
+:plain
+ $("#runner_#{@runner.id}").replaceWith("#{escape_javascript(render(@runner))}")
diff --git a/app/views/ci/commits/_commit.html.haml b/app/views/ci/commits/_commit.html.haml
new file mode 100644
index 00000000000..b24a3b826cf
--- /dev/null
+++ b/app/views/ci/commits/_commit.html.haml
@@ -0,0 +1,33 @@
+%tr.build
+ %td.status
+ = ci_status_with_icon(commit.status)
+ - if commit.running?
+ &middot;
+ = commit.stage
+
+
+ %td.build-link
+ = link_to ci_status_path(commit) do
+ %strong #{commit.short_sha}
+
+ %td.build-message
+ %span= truncate_first_line(commit.git_commit_message)
+
+ %td.build-branch
+ - unless @ref
+ %span
+ - commit.refs.each do |ref|
+ = link_to truncate(ref, length: 25), ci_project_path(@project, ref: ref)
+
+ %td.duration
+ - if commit.duration > 0
+ #{time_interval_in_words commit.duration}
+
+ %td.timestamp
+ - if commit.finished_at
+ %span #{time_ago_in_words commit.finished_at} ago
+
+ - if commit.project.coverage_enabled?
+ %td.coverage
+ - if commit.coverage
+ #{commit.coverage}%
diff --git a/app/views/ci/errors/show.haml b/app/views/ci/errors/show.haml
new file mode 100644
index 00000000000..2788112c835
--- /dev/null
+++ b/app/views/ci/errors/show.haml
@@ -0,0 +1,2 @@
+%h3.error Error
+= @error
diff --git a/app/views/ci/events/index.html.haml b/app/views/ci/events/index.html.haml
new file mode 100644
index 00000000000..779f49b3d3a
--- /dev/null
+++ b/app/views/ci/events/index.html.haml
@@ -0,0 +1,19 @@
+%h3.page-title Events
+
+%table.table
+ %thead
+ %tr
+ %th User ID
+ %th Description
+ %th When
+ - @events.each do |event|
+ %tr
+ %td
+ = event.user_id
+ %td
+ = event.description
+ %td.light
+ = time_ago_in_words event.updated_at
+ ago
+
+= paginate @events \ No newline at end of file
diff --git a/app/views/ci/lints/_create.html.haml b/app/views/ci/lints/_create.html.haml
new file mode 100644
index 00000000000..e2179e60f3e
--- /dev/null
+++ b/app/views/ci/lints/_create.html.haml
@@ -0,0 +1,39 @@
+- if @status
+ %p
+ %b Status:
+ syntax is correct
+ %i.fa.fa-ok.correct-syntax
+
+ %table.table.table-bordered
+ %thead
+ %tr
+ %th Parameter
+ %th Value
+ %tbody
+ - @stages.each do |stage|
+ - @builds.select { |build| build[:stage] == stage }.each do |build|
+ %tr
+ %td #{stage.capitalize} Job - #{build[:name]}
+ %td
+ %pre
+ = simple_format build[:script]
+
+ %br
+ %b Tag list:
+ = build[:tags]
+ %br
+ %b Refs only:
+ = build[:only] && build[:only].join(", ")
+ %br
+ %b Refs except:
+ = build[:except] && build[:except].join(", ")
+
+-else
+ %p
+ %b Status:
+ syntax is incorrect
+ %i.fa.fa-remove.incorrect-syntax
+ %b Error:
+ = @error
+
+
diff --git a/app/views/ci/lints/create.js.haml b/app/views/ci/lints/create.js.haml
new file mode 100644
index 00000000000..a96c0b11b6e
--- /dev/null
+++ b/app/views/ci/lints/create.js.haml
@@ -0,0 +1,2 @@
+:plain
+ $(".results").html("#{escape_javascript(render "create")}") \ No newline at end of file
diff --git a/app/views/ci/lints/show.html.haml b/app/views/ci/lints/show.html.haml
new file mode 100644
index 00000000000..a9b954771c5
--- /dev/null
+++ b/app/views/ci/lints/show.html.haml
@@ -0,0 +1,25 @@
+%h2 Check your .gitlab-ci.yml
+%hr
+
+= form_tag ci_lint_path, method: :post, remote: true do
+ .control-group
+ = label_tag :content, "Content of .gitlab-ci.yml", class: 'control-label'
+ .controls
+ = text_area_tag :content, nil, class: 'form-control span1', rows: 7, require: true
+
+ .control-group.clearfix
+ .controls.pull-left.prepend-top-10
+ = submit_tag "Validate", class: 'btn btn-success submit-yml'
+
+
+%p.text-center.loading
+ %i.fa.fa-refresh.fa-spin
+
+.results.prepend-top-20
+
+:coffeescript
+ $(".loading").hide()
+ $('form').bind 'ajax:beforeSend', ->
+ $(".loading").show()
+ $('form').bind 'ajax:complete', ->
+ $(".loading").hide()
diff --git a/app/views/ci/notify/build_fail_email.html.haml b/app/views/ci/notify/build_fail_email.html.haml
new file mode 100644
index 00000000000..69689a75022
--- /dev/null
+++ b/app/views/ci/notify/build_fail_email.html.haml
@@ -0,0 +1,19 @@
+- content_for :header do
+ %h1{style: "background: #c40834; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"}
+ GitLab CI (build failed)
+%h3
+ Project:
+ = link_to ci_project_url(@project) do
+ = @project.name
+
+%p
+ Commit link: #{gitlab_commit_link(@project, @build.commit.short_sha)}
+%p
+ Author: #{@build.commit.git_author_name}
+%p
+ Branch: #{@build.ref}
+%p
+ Message: #{@build.commit.git_commit_message}
+
+%p
+ Url: #{link_to @build.short_sha, namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build)}
diff --git a/app/views/ci/notify/build_fail_email.text.erb b/app/views/ci/notify/build_fail_email.text.erb
new file mode 100644
index 00000000000..6de5dc10f17
--- /dev/null
+++ b/app/views/ci/notify/build_fail_email.text.erb
@@ -0,0 +1,9 @@
+Build failed for <%= @project.name %>
+
+Status: <%= @build.status %>
+Commit: <%= @build.commit.short_sha %>
+Author: <%= @build.commit.git_author_name %>
+Branch: <%= @build.ref %>
+Message: <%= @build.commit.git_commit_message %>
+
+Url: <%= namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build) %>
diff --git a/app/views/ci/notify/build_success_email.html.haml b/app/views/ci/notify/build_success_email.html.haml
new file mode 100644
index 00000000000..4e3015a356b
--- /dev/null
+++ b/app/views/ci/notify/build_success_email.html.haml
@@ -0,0 +1,20 @@
+- content_for :header do
+ %h1{style: "background: #38CF5B; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"}
+ GitLab CI (build successful)
+
+%h3
+ Project:
+ = link_to ci_project_url(@project) do
+ = @project.name
+
+%p
+ Commit link: #{gitlab_commit_link(@project, @build.commit.short_sha)}
+%p
+ Author: #{@build.commit.git_author_name}
+%p
+ Branch: #{@build.ref}
+%p
+ Message: #{@build.commit.git_commit_message}
+
+%p
+ Url: #{link_to @build.short_sha, namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build)}
diff --git a/app/views/ci/notify/build_success_email.text.erb b/app/views/ci/notify/build_success_email.text.erb
new file mode 100644
index 00000000000..d0a43ae1c12
--- /dev/null
+++ b/app/views/ci/notify/build_success_email.text.erb
@@ -0,0 +1,9 @@
+Build successful for <%= @project.name %>
+
+Status: <%= @build.status %>
+Commit: <%= @build.commit.short_sha %>
+Author: <%= @build.commit.git_author_name %>
+Branch: <%= @build.ref %>
+Message: <%= @build.commit.git_commit_message %>
+
+Url: <%= namespace_project_build_url(@build.gl_project.namespace, @build.gl_project, @build) %>
diff --git a/app/views/ci/projects/_info.html.haml b/app/views/ci/projects/_info.html.haml
new file mode 100644
index 00000000000..1888e1bde93
--- /dev/null
+++ b/app/views/ci/projects/_info.html.haml
@@ -0,0 +1,2 @@
+- if no_runners_for_project?(@project)
+ = render 'no_runners'
diff --git a/app/views/ci/projects/_no_runners.html.haml b/app/views/ci/projects/_no_runners.html.haml
new file mode 100644
index 00000000000..c0a296fb17d
--- /dev/null
+++ b/app/views/ci/projects/_no_runners.html.haml
@@ -0,0 +1,8 @@
+.alert.alert-danger
+ %p
+ There are NO runners to build this project.
+ %br
+ You can add Specific runner for this project on Runners page
+
+ - if current_user.is_admin
+ or add Shared runner for whole application in admin are.
diff --git a/app/views/ci/projects/show.html.haml b/app/views/ci/projects/show.html.haml
new file mode 100644
index 00000000000..888b1ea41d5
--- /dev/null
+++ b/app/views/ci/projects/show.html.haml
@@ -0,0 +1,60 @@
+= render 'ci/shared/guide' unless @project.setup_finished?
+
+- if current_user && can?(current_user, :manage_project, gl_project) && !@project.any_runners?
+ .alert.alert-danger
+ Builds for this project wont be served unless you configure runners on
+ = link_to "Runners page", runners_path(@project.gl_project)
+
+%ul.nav.nav-tabs.append-bottom-20
+ %li{class: ref_tab_class}
+ = link_to 'All commits', ci_project_path(@project)
+ - @project.tracked_refs.each do |ref|
+ %li{class: ref_tab_class(ref)}
+ = link_to ref, ci_project_path(@project, ref: ref)
+
+ - if @ref && !@project.tracked_refs.include?(@ref)
+ %li{class: 'active'}
+ = link_to @ref, ci_project_path(@project, ref: @ref)
+
+ %li.pull-right
+ = link_to 'Go to project', project_path(gl_project), class: 'btn btn-sm'
+
+- if @ref
+ %p
+ Paste build status image for #{@ref} with next link
+ = link_to '#', class: 'badge-codes-toggle btn btn-default btn-xs' do
+ Status Badge
+ .badge-codes-block.bs-callout.bs-callout-info.hide
+ %p
+ Status badge for
+ %span.label.label-info #{@ref}
+ branch
+ %div
+ %label Markdown:
+ = text_field_tag 'badge_md', markdown_badge_code(@project, @ref), readonly: true, class: 'form-control'
+ %label Html:
+ = text_field_tag 'badge_html', html_badge_code(@project, @ref), readonly: true, class: 'form-control'
+
+
+
+
+%table.table.builds
+ %thead
+ %tr
+ %th Status
+ %th Commit
+ %th Message
+ %th Branch
+ %th Total duration
+ %th Finished at
+ - if @project.coverage_enabled?
+ %th Coverage
+
+ = render @commits
+
+= paginate @commits
+
+- if @commits.empty?
+ .bs-callout
+ %h4 No commits yet
+
diff --git a/app/views/ci/services/_form.html.haml b/app/views/ci/services/_form.html.haml
new file mode 100644
index 00000000000..9110aaa0528
--- /dev/null
+++ b/app/views/ci/services/_form.html.haml
@@ -0,0 +1,57 @@
+%h3.page-title
+ = @service.title
+ = boolean_to_icon @service.activated?
+
+%p= @service.description
+
+.back-link
+ = link_to ci_project_services_path(@project) do
+ &larr; to services
+
+%hr
+
+= form_for(@service, as: :service, url: ci_project_service_path(@project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |f|
+ - if @service.errors.any?
+ .alert.alert-danger
+ %ul
+ - @service.errors.full_messages.each do |msg|
+ %li= msg
+
+ - if @service.help.present?
+ .bs-callout
+ = @service.help
+
+ .form-group
+ = f.label :active, "Active", class: "control-label"
+ .col-sm-10
+ = f.check_box :active
+
+ - @service.fields.each do |field|
+ - name = field[:name]
+ - label = field[:label] || name
+ - value = @service.send(name)
+ - type = field[:type]
+ - placeholder = field[:placeholder]
+ - choices = field[:choices]
+ - default_choice = field[:default_choice]
+ - help = field[:help]
+
+ .form-group
+ = f.label label, class: "control-label"
+ .col-sm-10
+ - if type == 'text'
+ = f.text_field name, class: "form-control", placeholder: placeholder
+ - elsif type == 'textarea'
+ = f.text_area name, rows: 5, class: "form-control", placeholder: placeholder
+ - elsif type == 'checkbox'
+ = f.check_box name
+ - elsif type == 'select'
+ = f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
+ - if help
+ .light #{help}
+
+ .form-actions
+ = f.submit 'Save', class: 'btn btn-save'
+ &nbsp;
+ - if @service.valid? && @service.activated? && @service.can_test?
+ = link_to 'Test settings', test_ci_project_service_path(@project, @service.to_param), class: 'btn'
diff --git a/app/views/ci/services/edit.html.haml b/app/views/ci/services/edit.html.haml
new file mode 100644
index 00000000000..bcc5832792f
--- /dev/null
+++ b/app/views/ci/services/edit.html.haml
@@ -0,0 +1 @@
+= render 'form'
diff --git a/app/views/ci/services/index.html.haml b/app/views/ci/services/index.html.haml
new file mode 100644
index 00000000000..37e5723b541
--- /dev/null
+++ b/app/views/ci/services/index.html.haml
@@ -0,0 +1,22 @@
+%h3.page-title Project services
+%p.light Project services allow you to integrate GitLab CI with other applications
+
+%table.table
+ %thead
+ %tr
+ %th
+ %th Service
+ %th Desription
+ %th Last edit
+ - @services.sort_by(&:title).each do |service|
+ %tr
+ %td
+ = boolean_to_icon service.activated?
+ %td
+ = link_to edit_ci_project_service_path(@project, service.to_param) do
+ %strong= service.title
+ %td
+ = service.description
+ %td.light
+ = time_ago_in_words service.updated_at
+ ago
diff --git a/app/views/ci/shared/_guide.html.haml b/app/views/ci/shared/_guide.html.haml
new file mode 100644
index 00000000000..db2d7f2f4b6
--- /dev/null
+++ b/app/views/ci/shared/_guide.html.haml
@@ -0,0 +1,15 @@
+.bs-callout.help-callout
+ %h4 How to setup CI for this project
+
+ %ol
+ %li
+ Add at least one runner to the project.
+ Go to #{link_to 'Runners page', runners_path(@project.gl_project), target: :blank} for instructions.
+ %li
+ Put the .gitlab-ci.yml in the root of your repository. Examples can be found in #{link_to "Configuring project (.gitlab-ci.yml)", "http://doc.gitlab.com/ci/yaml/README.html", target: :blank}.
+ You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
+ %li
+ Visit #{link_to 'GitLab project settings', @project.gitlab_url + "/services/gitlab_ci/edit", target: :blank}
+ and press the "Test settings" button.
+ %li
+ Return to this page and refresh it, it should show a new build.
diff --git a/app/views/ci/shared/_no_runners.html.haml b/app/views/ci/shared/_no_runners.html.haml
new file mode 100644
index 00000000000..f56c37d9b37
--- /dev/null
+++ b/app/views/ci/shared/_no_runners.html.haml
@@ -0,0 +1,7 @@
+.alert.alert-danger
+ %p
+ Now you need Runners to process your builds.
+ %span
+ Checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} to install it
+
+
diff --git a/app/views/ci/user_sessions/new.html.haml b/app/views/ci/user_sessions/new.html.haml
new file mode 100644
index 00000000000..308b217ea78
--- /dev/null
+++ b/app/views/ci/user_sessions/new.html.haml
@@ -0,0 +1,8 @@
+.login-block
+ %h2 Login using GitLab account
+ %p.light
+ Make sure you have account on GitLab server
+ = link_to GitlabCi.config.gitlab_server.url, GitlabCi.config.gitlab_server.url, no_turbolink
+ %hr
+ = link_to "Login with GitLab", auth_ci_user_sessions_path(state: params[:state]), no_turbolink.merge( class: 'btn btn-login btn-success' )
+
diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml
index 213b5d65b3c..19d919f9b6a 100644
--- a/app/views/dashboard/_activities.html.haml
+++ b/app/views/dashboard/_activities.html.haml
@@ -1,13 +1,13 @@
.hidden-xs
= render "events/event_last_push", event: @last_push
+.gray-content-block
- if current_user
%ul.nav.nav-pills.event_filter.pull-right
%li.pull-right
- = link_to dashboard_path(:atom, { private_token: current_user.private_token }), class: 'rss-btn' do
+ = link_to dashboard_projects_path(:atom, { private_token: current_user.private_token }), class: 'rss-btn' do
%i.fa.fa-rss
-
= render 'shared/event_filter'
- %hr
+
.content_list
= spinner
diff --git a/app/views/dashboard/_activity_head.html.haml b/app/views/dashboard/_activity_head.html.haml
new file mode 100644
index 00000000000..9f4be025bf2
--- /dev/null
+++ b/app/views/dashboard/_activity_head.html.haml
@@ -0,0 +1,7 @@
+%ul.center-top-menu
+ %li{ class: ("active" unless params[:filter]) }
+ = link_to activity_dashboard_path, class: 'shortcuts-activity', data: {placement: 'right'} do
+ Your Projects
+ %li{ class: ("active" if params[:filter] == 'starred') }
+ = link_to activity_dashboard_path(filter: 'starred'), data: {placement: 'right'} do
+ Starred Projects
diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml
index 8a397a84e0e..64bd356f546 100644
--- a/app/views/dashboard/_groups_head.html.haml
+++ b/app/views/dashboard/_groups_head.html.haml
@@ -1,7 +1,7 @@
%ul.center-top-menu
- = nav_link(page: [dashboard_groups_path]) do
+ = nav_link(page: dashboard_groups_path) do
= link_to dashboard_groups_path, title: 'Your groups', data: {placement: 'right'} do
Your Groups
- = nav_link(page: [explore_groups_path]) do
- = link_to explore_groups_path, title: 'Explore groups', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = nav_link(page: explore_groups_path) do
+ = link_to explore_groups_path, title: 'Explore groups', data: {placement: 'bottom'} do
Explore Groups
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index f7be194c696..ed480b8caf8 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -1,10 +1,10 @@
%ul.center-top-menu
- = nav_link(path: ['dashboard#show', 'root#show']) do
- = link_to dashboard_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
+ = nav_link(path: ['projects#index', 'root#index']) do
+ = link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
Your Projects
= nav_link(page: starred_dashboard_projects_path) do
= link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do
Starred Projects
- = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do
- = link_to explore_root_path, title: 'Explore', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path], html_options: { class: 'hidden-xs' }) do
+ = link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do
Explore Projects
diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml
new file mode 100644
index 00000000000..0ae62d6f1b6
--- /dev/null
+++ b/app/views/dashboard/_snippets_head.html.haml
@@ -0,0 +1,7 @@
+%ul.center-top-menu
+ = nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do
+ = link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do
+ Your Snippets
+ = nav_link(page: explore_snippets_path) do
+ = link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do
+ Explore Snippets
diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml
index 7a5a093add5..aa57df14c23 100644
--- a/app/views/dashboard/activity.html.haml
+++ b/app/views/dashboard/activity.html.haml
@@ -1,6 +1,11 @@
= content_for :meta_tags do
- if current_user
- = auto_discovery_link_tag(:atom, dashboard_url(format: :atom, private_token: current_user.private_token), title: "All activity")
+ = auto_discovery_link_tag(:atom, dashboard_projects_url(format: :atom, private_token: current_user.private_token), title: "All activity")
+
+- page_title "Activity"
+- header_title "Activity", activity_dashboard_path
+
+= render 'dashboard/activity_head'
%section.activities
= render 'activities'
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index fbe523b4b66..c249f5cacec 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -1,14 +1,17 @@
- page_title "Groups"
+- header_title "Groups", dashboard_groups_path
= render 'dashboard/groups_head'
-.slead
- Group members have access to all group projects.
+.gray-content-block
- if current_user.can_create_group?
%span.pull-right.hidden-xs
- = link_to new_group_path, class: "btn btn-new btn-sm" do
+ = link_to new_group_path, class: "btn btn-new" do
%i.fa.fa-plus
New Group
-%ul.bordered-list
+ .title Welcome to the groups!
+ Group members have access to all group projects.
+
+%ul.content-list
- @group_members.each do |group_member|
- group = group_member.group
= render 'shared/groups/group', group: group, group_member: group_member
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index 94318d1bcf5..cd602e897b7 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -1,21 +1,17 @@
- page_title "Issues"
+- header_title "Issues", issues_dashboard_path(assignee_id: current_user.id)
= content_for :meta_tags do
- if current_user
= auto_discovery_link_tag(:atom, issues_dashboard_url(format: :atom, private_token: current_user.private_token), title: "#{current_user.name} issues")
-%h3.page-title
- Issues
-
-%p.light
- List all issues from all projects you have access to.
-%hr
.append-bottom-20
.pull-right
- if current_user
- .hidden-xs.pull-left
- = link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do
+ .hidden-xs.pull-left.prepend-top-20
+ = link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: '' do
%i.fa.fa-rss
= render 'shared/issuable/filter', type: :issues
+
= render 'shared/issues'
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index 90611d562b0..d1f332fa0d3 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -1,11 +1,6 @@
- page_title "Merge Requests"
-%h3.page-title
- Merge Requests
+- header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id)
-
-%p.light
- List all merge requests from all projects you have access to.
-%hr
.append-bottom-20
= render 'shared/issuable/filter', type: :merge_requests
= render 'shared/merge_requests'
diff --git a/app/views/dashboard/milestones/_milestone.html.haml b/app/views/dashboard/milestones/_milestone.html.haml
index d6f3e029a38..55080d6b3fe 100644
--- a/app/views/dashboard/milestones/_milestone.html.haml
+++ b/app/views/dashboard/milestones/_milestone.html.haml
@@ -1,20 +1,22 @@
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
- %h4
- = link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title)
+ .row
+ .col-sm-6
+ %strong
+ = link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title)
+ .col-sm-6
+ .pull-right.light #{milestone.percent_complete}% complete
.row
.col-sm-6
= link_to issues_dashboard_path(milestone_title: milestone.title) do
= pluralize milestone.issue_count, 'Issue'
- &nbsp;
+ &middot;
= link_to merge_requests_dashboard_path(milestone_title: milestone.title) do
= pluralize milestone.merge_requests_count, 'Merge Request'
- &nbsp;
- %span.light #{milestone.percent_complete}% complete
-
.col-sm-6
= milestone_progress_bar(milestone)
- %div
- - milestone.milestones.each do |milestone|
- = link_to milestone_path(milestone) do
- %span.label.label-gray
- = milestone.project.name_with_namespace
+ .row
+ .col-sm-6
+ - milestone.milestones.each do |milestone|
+ = link_to milestone_path(milestone) do
+ %span.label.label-gray
+ = milestone.project.name_with_namespace
diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml
index 9a9a5e139a4..21b25c3986e 100644
--- a/app/views/dashboard/milestones/index.html.haml
+++ b/app/views/dashboard/milestones/index.html.haml
@@ -1,21 +1,19 @@
- page_title "Milestones"
-%h3.page-title
- Milestones
- %span.pull-right #{@dashboard_milestones.count} milestones
+- header_title "Milestones", dashboard_milestones_path
-%p.light
- List all milestones from all projects you have access to.
-
-%hr
= render 'shared/milestones_filter'
+
+.gray-content-block
+ .oneline
+ List all milestones from all projects you have access to.
+
.milestones
- .panel.panel-default
- %ul.well-list
- - if @dashboard_milestones.blank?
- %li
- .nothing-here-block No milestones to show
- - else
- - @dashboard_milestones.each do |milestone|
- = render 'milestone', milestone: milestone
+ %ul.content-list
+ - if @dashboard_milestones.blank?
+ %li
+ .nothing-here-block No milestones to show
+ - else
+ - @dashboard_milestones.each do |milestone|
+ = render 'milestone', milestone: milestone
= paginate @dashboard_milestones, theme: "gitlab"
diff --git a/app/views/dashboard/_projects.html.haml b/app/views/dashboard/projects/_projects.html.haml
index ef9b9ce756a..e09e032a7f1 100644
--- a/app/views/dashboard/_projects.html.haml
+++ b/app/views/dashboard/projects/_projects.html.haml
@@ -4,7 +4,7 @@
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control'
- if current_user.can_create_project?
%span.input-group-btn
- = link_to new_project_path, class: 'btn btn-success' do
+ = link_to new_project_path, class: 'btn btn-green' do
New project
- = render 'shared/projects/list', projects: @projects
+ = render 'shared/projects/list', projects: @projects, ci: true
diff --git a/app/views/dashboard/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml
index 4e7d6639727..4e7d6639727 100644
--- a/app/views/dashboard/_zero_authorized_projects.html.haml
+++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml
diff --git a/app/views/dashboard/show.atom.builder b/app/views/dashboard/projects/index.atom.builder
index e9a612231d5..d2c51486841 100644
--- a/app/views/dashboard/show.atom.builder
+++ b/app/views/dashboard/projects/index.atom.builder
@@ -1,9 +1,9 @@
xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "Activity"
- xml.link href: dashboard_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
- xml.link href: dashboard_url, rel: "alternate", type: "text/html"
- xml.id dashboard_url
+ xml.link href: dashboard_projects_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
+ xml.link href: dashboard_projects_url, rel: "alternate", type: "text/html"
+ xml.id dashboard_projects_url
xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
@events.each do |event|
diff --git a/app/views/dashboard/show.html.haml b/app/views/dashboard/projects/index.html.haml
index 4cf2feb9aa6..7a16b811f6b 100644
--- a/app/views/dashboard/show.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -1,6 +1,9 @@
= content_for :meta_tags do
- if current_user
- = auto_discovery_link_tag(:atom, dashboard_url(format: :atom, private_token: current_user.private_token), title: "All activity")
+ = auto_discovery_link_tag(:atom, dashboard_projects_url(format: :atom, private_token: current_user.private_token), title: "All activity")
+
+- page_title "Projects"
+- header_title "Projects", root_path
= render 'dashboard/projects_head'
diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml
index 19f3975e530..f75f2e0a32a 100644
--- a/app/views/dashboard/projects/starred.html.haml
+++ b/app/views/dashboard/projects/starred.html.haml
@@ -1,24 +1,13 @@
- page_title "Starred Projects"
-= render 'dashboard/projects_head'
-
-- if @projects.any?
- = render 'shared/show_aside'
+- header_title "Projects", projects_path
- .dashboard.row
- %section.activities.col-md-7
- = render 'dashboard/activities'
- %aside.col-md-5
- .panel.panel-default.projects-list-holder
- .panel-heading.clearfix
- .input-group
- = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control'
- - if current_user.can_create_project?
- %span.input-group-btn
- = link_to new_project_path, class: 'btn btn-success' do
- New project
+= render 'dashboard/projects_head'
- = render 'shared/projects/list', projects: @projects, projects_limit: 20
+- if @last_push
+ = render "events/event_last_push", event: @last_push
+- if @projects.any?
+ = render 'projects'
- else
%h3 You don't have starred projects yet
%p.slead Visit project page and press on star icon and it will appear on this page.
diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml
new file mode 100644
index 00000000000..d3908062f43
--- /dev/null
+++ b/app/views/dashboard/snippets/index.html.haml
@@ -0,0 +1,38 @@
+- page_title "Snippets"
+- header_title "Snippets", dashboard_snippets_path
+
+= render 'dashboard/snippets_head'
+
+.gray-content-block
+ .pull-right
+ = link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do
+ Add new snippet
+
+ .oneline
+ Share code pastes with others out of git repository
+
+%ul.nav.nav-tabs.prepend-top-20
+ = nav_tab :scope, nil do
+ = link_to dashboard_snippets_path do
+ All
+ %span.badge
+ = current_user.snippets.count
+ = nav_tab :scope, 'are_private' do
+ = link_to dashboard_snippets_path(scope: 'are_private') do
+ Private
+ %span.badge
+ = current_user.snippets.are_private.count
+ = nav_tab :scope, 'are_internal' do
+ = link_to dashboard_snippets_path(scope: 'are_internal') do
+ Internal
+ %span.badge
+ = current_user.snippets.are_internal.count
+ = nav_tab :scope, 'are_public' do
+ = link_to dashboard_snippets_path(scope: 'are_public') do
+ Public
+ %span.badge
+ = current_user.snippets.are_public.count
+
+.my-snippets
+ = render 'snippets/snippets'
+
diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml
index 29ffe8a8be3..535e85869e5 100644
--- a/app/views/devise/passwords/new.html.haml
+++ b/app/views/devise/passwords/new.html.haml
@@ -6,7 +6,7 @@
.devise-errors
= devise_error_messages!
.clearfix.append-bottom-20
- = f.email_field :email, placeholder: "Email", class: "form-control", required: true, value: params[:user_email]
+ = f.email_field :email, placeholder: "Email", class: "form-control", required: true, value: params[:user_email], autofocus: true
.clearfix
= f.submit "Reset password", class: "btn-primary btn"
diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml
new file mode 100644
index 00000000000..4974bb7f7fb
--- /dev/null
+++ b/app/views/devise/sessions/_new_crowd.html.haml
@@ -0,0 +1,9 @@
+= form_tag(user_omniauth_authorize_path("crowd"), id: 'new_crowd_user' ) do
+ = text_field_tag :username, nil, {class: "form-control top", placeholder: "Username", autofocus: "autofocus"}
+ = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"}
+ - if devise_mapping.rememberable?
+ .remember-me.checkbox
+ %label{for: "remember_me"}
+ = check_box_tag :remember_me, '1', false, id: 'remember_me'
+ %span Remember me
+ = button_tag "Sign in", class: "btn-save btn" \ No newline at end of file
diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml
index bb5e479697d..41ad2c231d4 100644
--- a/app/views/devise/shared/_signin_box.html.haml
+++ b/app/views/devise/shared/_signin_box.html.haml
@@ -8,15 +8,21 @@
.login-body
- if form_based_providers.any?
%ul.nav.nav-tabs
+ - if crowd_enabled?
+ %li.active
+ = link_to "Crowd", "#tab-crowd", 'data-toggle' => 'tab'
- @ldap_servers.each_with_index do |server, i|
- %li{class: (:active if i.zero?)}
+ %li{class: (:active if i.zero? && !crowd_enabled?)}
= link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab'
- if signin_enabled?
%li
= link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab'
.tab-content
+ - if crowd_enabled?
+ %div.tab-pane.active{id: "tab-crowd"}
+ = render 'devise/sessions/new_crowd'
- @ldap_servers.each_with_index do |server, i|
- %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero?)}
+ %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero? && !crowd_enabled?)}
= render 'devise/sessions/new_ldap', server: server
- if signin_enabled?
%div#tab-signin.tab-pane
diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml
index 742b74a67c7..ad63841ccf3 100644
--- a/app/views/events/_commit.html.haml
+++ b/app/views/events/_commit.html.haml
@@ -1,5 +1,5 @@
%li.commit
.commit-row-title
= link_to truncate_sha(commit[:id]), namespace_project_commit_path(project.namespace, project, commit[:id]), class: "commit_short_id", alt: ''
- &nbsp;
+ &middot;
= gfm event_commit_title(commit[:message]), project: project
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 0faab4458e9..9aacc79d686 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -3,8 +3,8 @@
.event-item-timestamp
#{time_ago_with_tooltip(event.created_at)}
- = cache [event, "v1"] do
- = image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:''
+ = cache [event, "v2.1"] do
+ = image_tag avatar_icon(event.author_email, 46), class: "avatar s46", alt:''
- if event.created_project?
= render "events/event/created_project", event: event
- elsif event.push?
diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml
index 6a0c6cba41b..ffc37ad6178 100644
--- a/app/views/events/_event_last_push.html.haml
+++ b/app/views/events/_event_last_push.html.haml
@@ -1,14 +1,14 @@
- if show_last_push_widget?(event)
- .event-last-push
- .event-last-push-text
- %span You pushed to
- = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do
- %strong= event.ref_name
- %span at
- %strong= link_to_project event.project
- #{time_ago_with_tooltip(event.created_at)}
+ .gray-content-block.clear-block
+ .event-last-push
+ .event-last-push-text
+ %span You pushed to
+ = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do
+ %strong= event.ref_name
+ %span at
+ %strong= link_to_project event.project
+ #{time_ago_with_tooltip(event.created_at)}
- .pull-right
- = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do
- Create Merge Request
- %hr
+ .pull-right
+ = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do
+ Create Merge Request
diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml
index a39e62e9dac..4ecf1c33d2a 100644
--- a/app/views/events/event/_common.html.haml
+++ b/app/views/events/event/_common.html.haml
@@ -5,13 +5,14 @@
- if event.target
%strong= link_to "##{event.target_iid}", [event.project.namespace.becomes(Namespace), event.project, event.target]
- at
+
+ = event_preposition(event)
- if event.project
= link_to_project event.project
- else
= event.project_name
-
+
- if event.target.respond_to?(:title)
.event-body
.event-note
diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml
index 07bec1697f5..830fec0b4ab 100644
--- a/app/views/events/event/_note.html.haml
+++ b/app/views/events/event/_note.html.haml
@@ -4,7 +4,7 @@
= event.action_name
= event_note_title_html(event)
at
-
+
- if event.project
= link_to_project event.project
- else
@@ -13,7 +13,6 @@
.event-body
.event-note
.md
- %i.fa.fa-comment-o.event-note-icon
= event_note(event.target.note, project: event.project)
- note = event.target
- if note.attachment.url
diff --git a/app/views/explore/_head.html.haml b/app/views/explore/_head.html.haml
new file mode 100644
index 00000000000..d8a57560788
--- /dev/null
+++ b/app/views/explore/_head.html.haml
@@ -0,0 +1,6 @@
+.explore-title
+ %h3
+ Explore GitLab
+ %p.lead
+ Discover projects, groups and snippets. Share your projects with others
+ %br
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index 80acb914365..83d4d321c83 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -1,14 +1,19 @@
-- page_title "Groups"
+- page_title "Groups"
+- header_title "Groups", dashboard_groups_path
+
- if current_user
= render 'dashboard/groups_head'
-.clearfix.append-bottom-10
+- else
+ = render 'explore/head'
+
+.gray-content-block.clearfix
.pull-left
= form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f|
= hidden_field_tag :sort, @sort
.form-group
= search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "groups_search"
.form-group
- = button_tag 'Search', class: "btn btn-primary wide"
+ = button_tag 'Search', class: "btn btn-default"
.pull-right
.dropdown.inline
@@ -30,7 +35,7 @@
= link_to explore_groups_path(sort: sort_value_oldest_updated) do
= sort_title_oldest_updated
-%ul.bordered-list
+%ul.content-list
- @groups.each do |group|
= render 'shared/groups/group', group: group
- unless @groups.present?
diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml
index 4b91291caf4..5a3d689d1e5 100644
--- a/app/views/explore/projects/_filter.html.haml
+++ b/app/views/explore/projects/_filter.html.haml
@@ -3,7 +3,7 @@
.form-group
= search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "projects_search"
.form-group
- = button_tag 'Search', class: "btn btn-primary wide"
+ = button_tag 'Search', class: "btn btn-success"
.pull-right.hidden-sm.hidden-xs
- if current_user
diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml
index 0cfdf5cfd15..67e38ca3127 100644
--- a/app/views/explore/projects/index.html.haml
+++ b/app/views/explore/projects/index.html.haml
@@ -1,8 +1,12 @@
-- page_title "Projects"
+- page_title "Projects"
+- header_title "Projects", root_path
+
- if current_user
= render 'dashboard/projects_head'
-.clearfix
+- else
+ = render 'explore/head'
+
+.gray-content-block.clearfix
= render 'filter'
-%br
= render 'projects', projects: @projects
= paginate @projects, theme: "gitlab"
diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml
index 4a9fcae4bed..596cb0a96cd 100644
--- a/app/views/explore/projects/starred.html.haml
+++ b/app/views/explore/projects/starred.html.haml
@@ -1,11 +1,17 @@
-- page_title "Starred Projects"
+- page_title "Projects"
+- header_title "Projects", root_path
+
- if current_user
= render 'dashboard/projects_head'
+- else
+ = render 'explore/head'
+
.explore-trending-block
- .lead
- %i.fa.fa-star
- See most starred projects
+ .gray-content-block
.pull-right
= render 'explore/projects/dropdown'
+ .oneline
+ %i.fa.fa-star
+ See most starred projects
= render 'projects', projects: @starred_projects
= paginate @starred_projects, theme: 'gitlab'
diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml
index 4c7e7d44733..5ea6d81c5b9 100644
--- a/app/views/explore/projects/trending.html.haml
+++ b/app/views/explore/projects/trending.html.haml
@@ -1,16 +1,16 @@
-- page_title "Trending Projects"
+- page_title "Projects"
+- header_title "Projects", root_path
+
- if current_user
= render 'dashboard/projects_head'
-.explore-title
- %h3
- Explore GitLab
- %p.lead
- Discover projects and groups. Share your projects with others
-%hr
+- else
+ = render 'explore/head'
+
.explore-trending-block
- .lead
- %i.fa.fa-comments-o
- See most discussed projects for last month
+ .gray-content-block
.pull-right
= render 'explore/projects/dropdown'
+ .oneline
+ %i.fa.fa-comments-o
+ See most discussed projects for last month
= render 'projects', projects: @trending_projects
diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml
new file mode 100644
index 00000000000..7e4fa7d4873
--- /dev/null
+++ b/app/views/explore/snippets/index.html.haml
@@ -0,0 +1,18 @@
+- page_title "Snippets"
+- header_title "Snippets", snippets_path
+
+- if current_user
+ = render 'dashboard/snippets_head'
+- else
+ = render 'explore/head'
+
+.gray-content-block
+ - if current_user
+ .pull-right
+ = link_to new_snippet_path, class: "btn btn-new", title: "New Snippet" do
+ Add new snippet
+
+ .oneline
+ Public snippets created by you and other users are listed here
+
+= render 'snippets/snippets'
diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml
index b2e32ced5e0..2b27a88794d 100644
--- a/app/views/groups/_projects.html.haml
+++ b/app/views/groups/_projects.html.haml
@@ -4,7 +4,7 @@
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control'
- if can? current_user, :create_projects, @group
%span.input-group-btn
- = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-success' do
+ = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-green' do
New project
- = render 'shared/projects/list', projects: @projects, projects_limit: 20
+ = render 'shared/projects/list', projects: @projects, projects_limit: 20, stars: false
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 2ff4b7e23ea..ae8fc9f85f0 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -1,3 +1,6 @@
+- header_title group_title(@group, "Settings", edit_group_path(@group))
+- @blank_container = true
+
.panel.panel-default
.panel-heading
%strong= @group.name
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index dba395cc8fa..3a6d07ebddf 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -1,15 +1,13 @@
- page_title "Members"
+- header_title group_title(@group, "Members", group_group_members_path(@group))
- show_roles = should_user_see_group_roles?(current_user, @group)
-%h3.page-title
- Group members
- if show_roles
%p.light
Members of group have access to all group projects.
Read more about permissions
%strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
-%hr
.clearfix.js-toggle-container
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index f0d90782556..08d97e418a3 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -1,25 +1,24 @@
- page_title "Issues"
+- header_title group_title(@group, "Issues", issues_group_path(@group))
= content_for :meta_tags do
- if current_user
= auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues")
-%h3.page-title
- Issues
-%p.light
- Only issues from
- %strong #{@group.name}
- group are listed here.
- - if current_user
- To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
-%hr
-.append-bottom-20
+= render 'shared/issuable/filter', type: :issues
+.gray-content-block.second-block
.pull-right
- if current_user
.hidden-xs.pull-left
- = link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do
+ = link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token) do
%i.fa.fa-rss
+ %div
+ Only issues from
+ %strong #{@group.name}
+ group are listed here.
+ - if current_user
+ To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
- = render 'shared/issuable/filter', type: :issues
-= render 'shared/issues'
+.prepend-top-default
+ = render 'shared/issues'
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index ca85a158707..425ad8331bf 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -1,14 +1,13 @@
- page_title "Merge Requests"
-%h3.page-title
- Merge Requests
+- header_title group_title(@group, "Merge Requests", merge_requests_group_path(@group))
-%p.light
- Only merge requests from
- %strong #{@group.name}
- group are listed here.
- - if current_user
- To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
-%hr
-.append-bottom-20
- = render 'shared/issuable/filter', type: :merge_requests
-= render 'shared/merge_requests'
+= render 'shared/issuable/filter', type: :merge_requests
+.gray-content-block.second-block
+ %div
+ Only merge requests from
+ %strong #{@group.name}
+ group are listed here.
+ - if current_user
+ To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
+.prepend-top-default
+ = render 'shared/merge_requests'
diff --git a/app/views/groups/milestones/_header_title.html.haml b/app/views/groups/milestones/_header_title.html.haml
new file mode 100644
index 00000000000..d7fabf53587
--- /dev/null
+++ b/app/views/groups/milestones/_header_title.html.haml
@@ -0,0 +1 @@
+- header_title group_title(@group, "Milestones", group_milestones_path(@group))
diff --git a/app/views/groups/milestones/_milestone.html.haml b/app/views/groups/milestones/_milestone.html.haml
index ba30e6e07c6..41dffdd2fb8 100644
--- a/app/views/groups/milestones/_milestone.html.haml
+++ b/app/views/groups/milestones/_milestone.html.haml
@@ -1,25 +1,29 @@
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
- .pull-right
- - if can?(current_user, :admin_group, @group)
- - if milestone.closed?
- = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen"
- - else
- = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close"
- %h4
- = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title)
+ .row
+ .col-sm-6
+ %strong
+ = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title)
+ .col-sm-6
+ .pull-right.light #{milestone.percent_complete}% complete
.row
.col-sm-6
= link_to issues_group_path(@group, milestone_title: milestone.title) do
= pluralize milestone.issue_count, 'Issue'
- &nbsp;
+ &middot;
= link_to merge_requests_group_path(@group, milestone_title: milestone.title) do
= pluralize milestone.merge_requests_count, 'Merge Request'
- &nbsp;
- %span.light #{milestone.percent_complete}% complete
.col-sm-6
= milestone_progress_bar(milestone)
- %div
- - milestone.milestones.each do |milestone|
- = link_to milestone_path(milestone) do
- %span.label.label-gray
- = milestone.project.name
+ .row
+ .col-sm-6
+ %div
+ - milestone.milestones.each do |milestone|
+ = link_to milestone_path(milestone) do
+ %span.label.label-gray
+ = milestone.project.name
+ .col-sm-6
+ - if can?(current_user, :admin_group, @group)
+ - if milestone.closed?
+ = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen"
+ - else
+ = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-xs btn-close"
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index 385222fa5b7..2bbcad5fdfb 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -1,23 +1,17 @@
- page_title "Milestones"
-%h3.page-title
- Milestones
- %span.pull-right #{@group_milestones.count} milestones
+- header_title group_title(@group, "Milestones", group_milestones_path(@group))
-%p.light
+= render 'shared/milestones_filter'
+.gray-content-block
Only milestones from
%strong #{@group.name}
group are listed here.
-
-%hr
-
-= render 'shared/milestones_filter'
.milestones
- .panel.panel-default
- %ul.well-list
- - if @group_milestones.blank?
- %li
- .nothing-here-block No milestones to show
- - else
- - @group_milestones.each do |milestone|
- = render 'milestone', milestone: milestone
+ %ul.content-list
+ - if @group_milestones.blank?
+ %li
+ .nothing-here-block No milestones to show
+ - else
+ - @group_milestones.each do |milestone|
+ = render 'milestone', milestone: milestone
= paginate @group_milestones, theme: "gitlab"
diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml
index 8f2decb851f..0c213f42186 100644
--- a/app/views/groups/milestones/show.html.haml
+++ b/app/views/groups/milestones/show.html.haml
@@ -1,4 +1,6 @@
- page_title @group_milestone.title, "Milestones"
+= render "header_title"
+
%h4.page-title
.issue-box{ class: "issue-box-#{@group_milestone.closed? ? 'closed' : 'open'}" }
- if @group_milestone.closed?
diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml
index d06cfa7ff9f..f1d507a50c7 100644
--- a/app/views/groups/projects.html.haml
+++ b/app/views/groups/projects.html.haml
@@ -1,4 +1,6 @@
- page_title "Projects"
+- header_title group_title(@group, "Projects", projects_group_path(@group))
+
.panel.panel-default
.panel-heading
%strong= @group.name
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 0577f4ec142..a9ba9d2ba10 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -1,3 +1,6 @@
+- unless can?(current_user, :read_group, @group)
+ - @disable_search_panel = true
+
= content_for :meta_tags do
- if current_user
= auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity")
@@ -16,22 +19,25 @@
= render 'shared/show_aside'
- .row
- %section.activities.col-md-7
- .hidden-xs
- - if current_user
- = render "events/event_last_push", event: @last_push
-
+ - if can?(current_user, :read_group, @group)
+ .row
+ %section.activities.col-md-7
+ .hidden-xs
- if current_user
+ = render "events/event_last_push", event: @last_push
+
%ul.nav.nav-pills.event_filter.pull-right
%li
= link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'rss-btn' do
%i.fa.fa-rss
- = render 'shared/event_filter'
- %hr
+ = render 'shared/event_filter'
+ %hr
- .content_list
- = spinner
- %aside.side.col-md-5
- = render "projects", projects: @projects
+ .content_list
+ = spinner
+ %aside.side.col-md-5
+ = render "projects", projects: @projects
+ - else
+ %p
+ This group does not have public projects
diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml
index bf4b7234b21..57bc91ea5a9 100644
--- a/app/views/help/index.html.haml
+++ b/app/views/help/index.html.haml
@@ -1,14 +1,15 @@
%div
%h1
GitLab
- %span= Gitlab::VERSION
- %small= Gitlab::REVISION
- - if current_application_settings.version_check_enabled
+ Community Edition
+ - if user_signed_in?
+ %span= Gitlab::VERSION
+ %small= Gitlab::REVISION
= version_status_badge
%p.slead
GitLab is open source software to collaborate on code.
%br
- Manage git repositories with fine grained access controls that keep your code secure.
+ Manage git repositories with fine-grained access controls that keep your code secure.
%br
Perform code reviews and enhance collaboration with merge requests.
%br
@@ -17,6 +18,9 @@
Used by more than 100,000 organizations, GitLab is the most popular solution to manage git repositories on-premises.
%br
Read more about GitLab at #{link_to promo_host, promo_url, target: '_blank'}.
+ - if current_application_settings.help_page_text.present?
+ %hr
+ = markdown(current_application_settings.help_page_text)
%hr
@@ -24,29 +28,14 @@
.col-md-8
.documentation-index
= preserve do
- - readme_text = File.read(Rails.root.join("doc", "README.md"))
- - text = readme_text.dup
- - readme_text.scan(/\]\(([^(]+)\)/) { |match| text.gsub!(match.first, "help/#{match.first}") }
- = markdown text
-
+ = markdown(@help_index)
.col-md-4
.panel.panel-default
.panel-heading
Quick help
%ul.well-list
- %li
- See our website for
- = link_to 'getting help', promo_url + '/getting-help/'
- %li
- Use the
- = link_to 'search bar', '#', onclick: 'Shortcuts.focusSearch(event)'
- on the top of this page
- %li
- Use
- = link_to 'shortcuts', '#', onclick: 'Shortcuts.showHelp(event)'
- %li
- Get a support
- = link_to 'subscription', 'https://about.gitlab.com/pricing/'
- %li
- = link_to 'Compare', 'https://about.gitlab.com/features/#compare'
- GitLab editions
+ %li= link_to 'See our website for getting help', promo_url + '/getting-help/'
+ %li= link_to 'Use the search bar on the top of this page', '#', onclick: 'Shortcuts.focusSearch(event)'
+ %li= link_to 'Use shortcuts', '#', onclick: 'Shortcuts.showHelp(event)'
+ %li= link_to 'Get a support subscription', 'https://about.gitlab.com/pricing/'
+ %li= link_to 'Compare GitLab editions', 'https://about.gitlab.com/features/#compare'
diff --git a/app/views/help/show.html.haml b/app/views/help/show.html.haml
index 8551496b98a..0398afb4c1d 100644
--- a/app/views/help/show.html.haml
+++ b/app/views/help/show.html.haml
@@ -1,3 +1,3 @@
- page_title @file.humanize, *@category.split("/").reverse.map(&:humanize)
.documentation.wiki
- = markdown @markdown.gsub('$your_email', current_user.email)
+ = markdown @markdown.gsub('$your_email', current_user.try(:email) || "email@example.com")
diff --git a/app/views/import/fogbugz/new.html.haml b/app/views/import/fogbugz/new.html.haml
new file mode 100644
index 00000000000..e1bb88ca4ed
--- /dev/null
+++ b/app/views/import/fogbugz/new.html.haml
@@ -0,0 +1,25 @@
+- page_title "FogBugz Import"
+%h3.page-title
+ %i.fa.fa-bug
+ Import projects from FogBugz
+%hr
+
+= form_tag callback_import_fogbugz_path, class: 'form-horizontal' do
+ %p
+ To get started you enter your FogBugz URL and login information below.
+ In the next steps, you'll be able to map users and select the projects
+ you want to import.
+ .form-group
+ = label_tag :uri, 'FogBugz URL', class: 'control-label'
+ .col-sm-4
+ = text_field_tag :uri, nil, placeholder: 'https://mycompany.fogbugz.com', class: 'form-control'
+ .form-group
+ = label_tag :email, 'FogBugz Email', class: 'control-label'
+ .col-sm-4
+ = text_field_tag :email, nil, class: 'form-control'
+ .form-group
+ = label_tag :password, 'FogBugz Password', class: 'control-label'
+ .col-sm-4
+ = password_field_tag :password, nil, class: 'form-control'
+ .form-actions
+ = submit_tag 'Continue to the next step', class: 'btn btn-create'
diff --git a/app/views/import/fogbugz/new_user_map.html.haml b/app/views/import/fogbugz/new_user_map.html.haml
new file mode 100644
index 00000000000..25cebfb3665
--- /dev/null
+++ b/app/views/import/fogbugz/new_user_map.html.haml
@@ -0,0 +1,49 @@
+- page_title 'User map', 'FogBugz import'
+%h3.page-title
+ %i.fa.fa-bug
+ Import projects from FogBugz
+%hr
+
+= form_tag create_user_map_import_fogbugz_path, class: 'form-horizontal' do
+ %p
+ Customize how FogBugz email addresses and usernames are imported into GitLab.
+ In the next step, you'll be able to select the projects you want to import.
+ %p
+ The user map is a mapping of the FogBugz users that participated on your projects to the way their email address and usernames wil be imported into GitLab. You can change this by populating the table below.
+ %ul
+ %li
+ %strong Default: Map a FogBugz account ID to a full name
+ %p
+ An empty GitLab User field will add the FogBugz user's full name
+ (e.g. "By John Smith") in the description of all issues and comments.
+ It will also associate and/or assign these issues and comments with
+ the project creator.
+ %li
+ %strong Map a FogBugz account ID to a GitLab user
+ %p
+ Selecting a GitLab user will add a link to the GitLab user in the descriptions
+ of issues and comments (e.g. "By <a href="#">@johnsmith</a>"). It will also
+ associate and/or assign these issues and comments with the selected user.
+
+ %table.table
+ %thead
+ %tr
+ %th ID
+ %th Name
+ %th Email
+ %th GitLab User
+ %tbody
+ - @user_map.each do |id, user|
+ %tr
+ %td= id
+ %td= text_field_tag "users[#{id}][name]", user[:name], class: 'form-control'
+ %td= text_field_tag "users[#{id}][email]", user[:email], class: 'form-control'
+ %td
+ = users_select_tag("users[#{id}][gitlab_user]", class: 'custom-form-control',
+ scope: :all, email_user: true, selected: user[:gitlab_user])
+
+ .form-actions
+ = submit_tag 'Continue to the next step', class: 'btn btn-create'
+
+:coffeescript
+ new UsersSelect()
diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml
new file mode 100644
index 00000000000..f179ece402d
--- /dev/null
+++ b/app/views/import/fogbugz/status.html.haml
@@ -0,0 +1,51 @@
+- page_title "FogBugz import"
+%h3.page-title
+ %i.fa.fa-bug
+ Import projects from FogBugz
+
+- if @repos.any?
+ %p.light
+ Select projects you want to import.
+ %p.light
+ Optionally, you can
+ = link_to 'customize', new_user_map_import_fogbugz_path
+ how FogBugz email addresses and usernames are imported into GitLab.
+ %hr
+ %p
+ = button_tag 'Import all projects', class: 'btn btn-success js-import-all'
+
+%table.table.import-jobs
+ %thead
+ %tr
+ %th From FogBugz
+ %th To GitLab
+ %th Status
+ %tbody
+ - @already_added_projects.each do |project|
+ %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
+ %td
+ = project.import_source
+ %td
+ %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
+ %td.job-status
+ - if project.import_status == 'finished'
+ %span
+ %i.fa.fa-check
+ done
+ - elsif project.import_status == 'started'
+ %i.fa.fa-spinner.fa-spin
+ started
+ - else
+ = project.human_import_status_name
+
+ - @repos.each do |repo|
+ %tr{id: "repo_#{repo.id}"}
+ %td
+ = repo.name
+ %td.import-target
+ = "#{current_user.username}/#{repo.name}"
+ %td.import-actions.job-status
+ = button_tag "Import", class: "btn js-add-to-import"
+
+:coffeescript
+ new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}")
diff --git a/app/views/kaminari/gitlab/_first_page.html.haml b/app/views/kaminari/gitlab/_first_page.html.haml
index 41c9c0b3af6..ada7306d98d 100644
--- a/app/views/kaminari/gitlab/_first_page.html.haml
+++ b/app/views/kaminari/gitlab/_first_page.html.haml
@@ -5,5 +5,5 @@
-# num_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
-%span.first
+%li.first
= link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, remote: remote
diff --git a/app/views/kaminari/gitlab/_last_page.html.haml b/app/views/kaminari/gitlab/_last_page.html.haml
index b03a206224c..3431d029bcc 100644
--- a/app/views/kaminari/gitlab/_last_page.html.haml
+++ b/app/views/kaminari/gitlab/_last_page.html.haml
@@ -5,5 +5,5 @@
-# num_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
-%span.last
+%li.last
= link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, {remote: remote}
diff --git a/app/views/kaminari/gitlab/_paginator.html.haml b/app/views/kaminari/gitlab/_paginator.html.haml
index 4f7996e4996..2f645186921 100644
--- a/app/views/kaminari/gitlab/_paginator.html.haml
+++ b/app/views/kaminari/gitlab/_paginator.html.haml
@@ -7,11 +7,16 @@
-# paginator: the paginator that renders the pagination tags inside
= paginator.render do
%div.gl-pagination
- %ul.pagination
- = prev_page_tag unless current_page.first?
+ %ul.pagination.clearfix
+ - unless current_page.first?
+ = first_page_tag unless num_pages < 5 # As kaminari will always show the first 5 pages
+ = prev_page_tag
- each_page do |page|
- if page.left_outer? || page.right_outer? || page.inside_window?
= page_tag page
- elsif !page.was_truncated?
= gap_tag
- = next_page_tag unless current_page.last?
+ - unless current_page.last?
+ = next_page_tag
+ = last_page_tag unless num_pages < 5
+
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 397649dacf8..74174a72f5a 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -3,6 +3,7 @@
%meta{charset: "utf-8"}
%meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'}
%meta{content: "GitLab Community Edition", name: "description"}
+ %meta{name: 'referrer', content: 'origin-when-cross-origin'}
%title= page_title
diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml
index 3c58f10e759..035fe0056d3 100644
--- a/app/views/layouts/_init_auto_complete.html.haml
+++ b/app/views/layouts/_init_auto_complete.html.haml
@@ -1,3 +1,4 @@
+- project = @target_project || @project
:javascript
- GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(@project.namespace, @project, type: @noteable.class, type_id: params[:id])}"
+ GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(project.namespace, project, type: @noteable.class, type_id: params[:id])}"
GitLab.GfmAutoComplete.setup();
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 0104d7198df..2468687b56d 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -6,20 +6,24 @@
= brand_header_logo
.gitlab-text-container
%h3 GitLab
+
- if defined?(sidebar) && sidebar
= render "layouts/nav/#{sidebar}"
- elsif current_user
= render 'layouts/nav/dashboard'
+ - else
+ = render 'layouts/nav/explore'
+
.collapse-nav
= render partial: 'layouts/collapse_button'
- if current_user
= link_to current_user, class: 'sidebar-user' do
- = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s32'
+ = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s36'
.username
= current_user.username
.content-wrapper
- %div{ class: fluid_layout ? "container-fluid" : "container-fluid container-limited" }
+ = render "layouts/flash"
+ %div{ class: container_class }
.content
- = render "layouts/flash"
.clearfix
= yield
diff --git a/app/views/layouts/ci/_info.html.haml b/app/views/layouts/ci/_info.html.haml
new file mode 100644
index 00000000000..24c68a6dbf5
--- /dev/null
+++ b/app/views/layouts/ci/_info.html.haml
@@ -0,0 +1,2 @@
+- if current_user && current_user.is_admin? && Ci::Runner.count.zero?
+ = render 'ci/shared/no_runners'
diff --git a/app/views/layouts/ci/_nav_admin.html.haml b/app/views/layouts/ci/_nav_admin.html.haml
new file mode 100644
index 00000000000..af2545a22d8
--- /dev/null
+++ b/app/views/layouts/ci/_nav_admin.html.haml
@@ -0,0 +1,33 @@
+%ul.nav.nav-sidebar
+ = nav_link do
+ = link_to admin_root_path, title: 'Back to admin', data: {placement: 'right'}, class: 'back-link' do
+ = icon('caret-square-o-left fw')
+ %span
+ Back to admin
+
+ %li.separate-item
+ = nav_link path: 'projects#index' do
+ = link_to ci_admin_projects_path do
+ = icon('list-alt fw')
+ Projects
+ = nav_link path: 'events#index' do
+ = link_to ci_admin_events_path do
+ = icon('book fw')
+ Events
+ = nav_link path: ['runners#index', 'runners#show'] do
+ = link_to ci_admin_runners_path do
+ = icon('cog fw')
+ Runners
+ %small.pull-right
+ = Ci::Runner.count(:all)
+ = nav_link path: 'builds#index' do
+ = link_to ci_admin_builds_path do
+ = icon('link fw')
+ Builds
+ %small.pull-right
+ = Ci::Build.count(:all)
+ = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
+ = link_to ci_admin_application_settings_path do
+ = icon('cogs fw')
+ %span
+ Settings
diff --git a/app/views/layouts/ci/_nav_project.html.haml b/app/views/layouts/ci/_nav_project.html.haml
new file mode 100644
index 00000000000..545abc23d99
--- /dev/null
+++ b/app/views/layouts/ci/_nav_project.html.haml
@@ -0,0 +1,23 @@
+%ul.nav.nav-sidebar
+ = nav_link do
+ = link_to project_path(@project.gl_project), title: 'Back to project', data: {placement: 'right'}, class: 'back-link' do
+ = icon('caret-square-o-left fw')
+ %span
+ Back to project
+ %li.separate-item
+ = nav_link path: ['projects#show', 'commits#show', 'builds#show'] do
+ = link_to ci_project_path(@project) do
+ = icon('list-alt fw')
+ %span
+ Commits
+ %span.count= @project.commits.count
+ = nav_link path: ['services#index', 'services#edit'] do
+ = link_to ci_project_services_path(@project) do
+ = icon('share fw')
+ %span
+ Services
+ = nav_link path: 'events#index' do
+ = link_to ci_project_events_path(@project) do
+ = icon('book fw')
+ %span
+ Events
diff --git a/app/views/layouts/ci/_page.html.haml b/app/views/layouts/ci/_page.html.haml
new file mode 100644
index 00000000000..bb5ec727bff
--- /dev/null
+++ b/app/views/layouts/ci/_page.html.haml
@@ -0,0 +1,27 @@
+.page-with-sidebar{ class: nav_sidebar_class }
+ = render "layouts/broadcast"
+ .sidebar-wrapper.nicescroll
+ .header-logo
+ = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = brand_header_logo
+ .gitlab-text-container
+ %h3 GitLab
+
+ - if defined?(sidebar) && sidebar
+ = render "layouts/ci/#{sidebar}"
+ - elsif current_user
+ = render 'layouts/nav/dashboard'
+ .collapse-nav
+ = render partial: 'layouts/collapse_button'
+ - if current_user
+ = link_to current_user, class: 'sidebar-user' do
+ = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s36'
+ .username
+ = current_user.username
+ .content-wrapper
+ = render "layouts/flash"
+ = render 'layouts/ci/info'
+ %div{ class: container_class }
+ .content
+ .clearfix
+ = yield
diff --git a/app/views/layouts/ci/admin.html.haml b/app/views/layouts/ci/admin.html.haml
new file mode 100644
index 00000000000..c8cb185d28c
--- /dev/null
+++ b/app/views/layouts/ci/admin.html.haml
@@ -0,0 +1,11 @@
+!!! 5
+%html{ lang: "en"}
+ = render 'layouts/head'
+ %body{class: "ci-body #{user_application_theme}", 'data-page' => body_data_page}
+ - header_title = "Admin area"
+ - if current_user
+ = render "layouts/header/default", title: header_title
+ - else
+ = render "layouts/header/public", title: header_title
+
+ = render 'layouts/ci/page', sidebar: 'nav_admin'
diff --git a/app/views/layouts/ci/application.html.haml b/app/views/layouts/ci/application.html.haml
new file mode 100644
index 00000000000..38023468d0b
--- /dev/null
+++ b/app/views/layouts/ci/application.html.haml
@@ -0,0 +1,11 @@
+!!! 5
+%html{ lang: "en"}
+ = render 'layouts/head'
+ %body{class: "ci-body #{user_application_theme}", 'data-page' => body_data_page}
+ - header_title = "Continuous Integration"
+ - if current_user
+ = render "layouts/header/default", title: header_title
+ - else
+ = render "layouts/header/public", title: header_title
+
+ = render 'layouts/ci/page'
diff --git a/app/views/layouts/ci/notify.html.haml b/app/views/layouts/ci/notify.html.haml
new file mode 100644
index 00000000000..270b206df5e
--- /dev/null
+++ b/app/views/layouts/ci/notify.html.haml
@@ -0,0 +1,19 @@
+%html{lang: "en"}
+ %head
+ %meta{content: "text/html; charset=utf-8", "http-equiv" => "Content-Type"}
+ %title
+ GitLab CI
+
+ %body
+ = yield :header
+
+ %table{align: "left", border: "0", cellpadding: "0", cellspacing: "0", style: "padding: 10px 0;", width: "100%"}
+ %tr
+ %td{align: "left", style: "margin: 0; padding: 10px;"}
+ = yield
+ %br
+ %tr
+ %td{align: "left", style: "margin: 0; padding: 10px;"}
+ %p{style: "font-size:small;color:#777"}
+ - if @project
+ You're receiving this notification because you are the one who triggered a build on the #{@project.name} project.
diff --git a/app/views/layouts/ci/project.html.haml b/app/views/layouts/ci/project.html.haml
new file mode 100644
index 00000000000..15478c3f5bc
--- /dev/null
+++ b/app/views/layouts/ci/project.html.haml
@@ -0,0 +1,11 @@
+!!! 5
+%html{ lang: "en"}
+ = render 'layouts/head'
+ %body{class: "ci-body #{user_application_theme}", 'data-page' => body_data_page}
+ - header_title @project.name, ci_project_path(@project)
+ - if current_user
+ = render "layouts/header/default", title: header_title
+ - else
+ = render "layouts/header/public", title: header_title
+
+ = render 'layouts/ci/page', sidebar: 'nav_project'
diff --git a/app/views/layouts/dashboard.html.haml b/app/views/layouts/dashboard.html.haml
index c72eca10bf4..cb96bcc2cf4 100644
--- a/app/views/layouts/dashboard.html.haml
+++ b/app/views/layouts/dashboard.html.haml
@@ -1,5 +1,5 @@
- page_title "Dashboard"
-- header_title "Dashboard", root_path
+- header_title "Dashboard", root_path unless header_title
- sidebar "dashboard"
= render template: "layouts/application"
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index 1987bf1592a..95e077c339f 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -31,5 +31,5 @@
.container
.footer-links
= link_to "Explore", explore_root_path
- = link_to "Documentation", "http://doc.gitlab.com/"
+ = link_to "Help", help_path
= link_to "About GitLab", "https://about.gitlab.com/"
diff --git a/app/views/layouts/explore.html.haml b/app/views/layouts/explore.html.haml
index 17fee9c510d..df65792be73 100644
--- a/app/views/layouts/explore.html.haml
+++ b/app/views/layouts/explore.html.haml
@@ -1,8 +1,5 @@
- page_title "Explore"
-- if current_user
- - header_title "Dashboard", root_path
-- else
+- unless current_user
- header_title "Explore GitLab", explore_root_path
-- sidebar "dashboard"
= render template: "layouts/application"
diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml
index db7dbf9bfe3..31888c5580e 100644
--- a/app/views/layouts/group.html.haml
+++ b/app/views/layouts/group.html.haml
@@ -1,5 +1,5 @@
- page_title @group.name
-- header_title @group.name, group_path(@group)
-- sidebar "group" unless sidebar
+- header_title group_title(@group) unless header_title
+- sidebar "group" unless sidebar
= render template: "layouts/application"
diff --git a/app/views/layouts/group_settings.html.haml b/app/views/layouts/group_settings.html.haml
index e303a561628..a1a1fc2f858 100644
--- a/app/views/layouts/group_settings.html.haml
+++ b/app/views/layouts/group_settings.html.haml
@@ -1,4 +1,5 @@
- page_title "Settings"
+- header_title group_title(@group, "Settings", edit_group_path(@group))
- sidebar "group_settings"
= render template: "layouts/group"
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 0b630b55c70..c31b1cbe9a8 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -1,5 +1,5 @@
%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
- %div{ class: fluid_layout ? "container-fluid" : "container-fluid container-limited" }
+ %div{ class: fluid_layout ? "container-fluid" : "container-fluid" }
.header-content
%button.navbar-toggle{type: 'button'}
%span.sr-only Toggle navigation
@@ -7,8 +7,9 @@
.navbar-collapse.collapse
%ul.nav.navbar-nav.pull-right
- %li.hidden-sm.hidden-xs
- = render 'layouts/search'
+ - unless @disable_search_panel
+ %li.hidden-sm.hidden-xs
+ = render 'layouts/search'
%li.visible-sm.visible-xs
= link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do
= icon('search')
@@ -17,7 +18,7 @@
= link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do
= icon('wrench fw')
- if current_user.can_create_project?
- %li.hidden-xs
+ %li
= link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do
= icon('plus fw')
%li
diff --git a/app/views/layouts/header/_public.html.haml b/app/views/layouts/header/_public.html.haml
index af4b9ba58f6..a6a26518a0e 100644
--- a/app/views/layouts/header/_public.html.haml
+++ b/app/views/layouts/header/_public.html.haml
@@ -1,9 +1,9 @@
%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
- %div{ class: fluid_layout ? "container-fluid" : "container-fluid container-limited" }
+ %div{ class: fluid_layout ? "container-fluid" : "container-fluid" }
.header-content
- unless current_controller?('sessions')
.pull-right
- = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success btn-sm'
+ = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success'
%h1.title= title
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index 2065be3828a..2079feeeab6 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -4,7 +4,7 @@
= icon('dashboard fw')
%span
Overview
- = nav_link(controller: :projects) do
+ = nav_link(controller: [:admin, :projects]) do
= link_to admin_namespaces_projects_path, title: 'Projects', data: {placement: 'right'} do
= icon('cube fw')
%span
@@ -24,6 +24,11 @@
= icon('key fw')
%span
Deploy Keys
+ = nav_link do
+ = link_to ci_admin_projects_path, title: 'Continuous Integration', data: {placement: 'right'} do
+ = icon('building fw')
+ %span
+ Continuous Integration
= nav_link(controller: :logs) do
= link_to admin_logs_path, title: 'Logs', data: {placement: 'right'} do
= icon('file-text fw')
@@ -57,6 +62,12 @@
%span
Service Templates
+ = nav_link(controller: :labels) do
+ = link_to admin_labels_path, title: 'Labels', data: {placement: 'right'} do
+ = icon('tags fw')
+ %span
+ Labels
+
= nav_link(controller: :abuse_reports) do
= link_to admin_abuse_reports_path, title: "Abuse reports" do
= icon('exclamation-circle fw')
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 0cf1c3d5d27..b1a1d531846 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,31 +1,30 @@
%ul.nav.nav-sidebar
- = nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
- = link_to (current_user ? root_path : explore_root_path), title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
+ = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: 'home'}) do
+ = link_to dashboard_projects_path, title: 'Projects', data: {placement: 'right'} do
= icon('home fw')
%span
Projects
= nav_link(path: 'dashboard#activity') do
- = link_to activity_dashboard_path, title: 'Activity', data: {placement: 'right'} do
+ = link_to activity_dashboard_path, class: 'shortcuts-activity', title: 'Activity', data: {placement: 'right'} do
= icon('dashboard fw')
%span
Activity
= nav_link(controller: :groups) do
- = link_to (current_user ? dashboard_groups_path : explore_groups_path), title: 'Groups', data: {placement: 'right'} do
+ = link_to dashboard_groups_path, title: 'Groups', data: {placement: 'right'} do
= icon('group fw')
%span
Groups
- - if current_user
- = nav_link(controller: :milestones) do
- = link_to dashboard_milestones_path, title: 'Milestones', data: {placement: 'right'} do
- = icon('clock-o fw')
- %span
- Milestones
- = nav_link(path: 'dashboard#issues') do
- = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues', data: {placement: 'right'} do
- = icon('exclamation-circle fw')
- %span
- Issues
- %span.count= current_user.assigned_issues.opened.count
+ = nav_link(controller: :milestones) do
+ = link_to dashboard_milestones_path, title: 'Milestones', data: {placement: 'right'} do
+ = icon('clock-o fw')
+ %span
+ Milestones
+ = nav_link(path: 'dashboard#issues') do
+ = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues', data: {placement: 'right'} do
+ = icon('exclamation-circle fw')
+ %span
+ Issues
+ %span.count= current_user.assigned_issues.opened.count
= nav_link(path: 'dashboard#merge_requests') do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests', data: {placement: 'right'} do
= icon('tasks fw')
@@ -33,18 +32,19 @@
Merge Requests
%span.count= current_user.assigned_merge_requests.opened.count
= nav_link(controller: :snippets) do
- = link_to (current_user ? user_snippets_path(current_user) : snippets_path), title: 'Your snippets', data: {placement: 'right'} do
+ = link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do
= icon('clipboard fw')
%span
Snippets
- - if current_user
- = nav_link(controller: :profile) do
- = link_to profile_path, title: 'Profile settings', data: {toggle: 'tooltip', placement: 'bottom'} do
- = icon('user fw')
- %span
- Profile Settings
= nav_link(controller: :help) do
= link_to help_path, title: 'Help', data: {placement: 'right'} do
= icon('question-circle fw')
%span
Help
+
+ %li.separate-item
+ = nav_link(controller: :profile) do
+ = link_to profile_path, title: 'Profile settings', data: {placement: 'bottom'} do
+ = icon('user fw')
+ %span
+ Profile Settings
diff --git a/app/views/layouts/nav/_explore.html.haml b/app/views/layouts/nav/_explore.html.haml
new file mode 100644
index 00000000000..21e565972a7
--- /dev/null
+++ b/app/views/layouts/nav/_explore.html.haml
@@ -0,0 +1,21 @@
+%ul.nav.nav-sidebar
+ = nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
+ = link_to explore_root_path, title: 'Projects', data: {placement: 'right'} do
+ = icon('home fw')
+ %span
+ Projects
+ = nav_link(controller: :groups) do
+ = link_to explore_groups_path, title: 'Groups', data: {placement: 'right'} do
+ = icon('group fw')
+ %span
+ Groups
+ = nav_link(controller: :snippets) do
+ = link_to explore_snippets_path, title: 'Snippets', data: {placement: 'right'} do
+ = icon('clipboard fw')
+ %span
+ Snippets
+ = nav_link(controller: :help) do
+ = link_to help_path, title: 'Help', data: {placement: 'right'} do
+ = icon('question-circle fw')
+ %span
+ Help
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 695ce68a201..eb35af22b93 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -3,7 +3,7 @@
= link_to root_path, title: 'Back to dashboard', data: {placement: 'right'}, class: 'back-link' do
= icon('caret-square-o-left fw')
%span
- Back to Dashboard
+ Back to dashboard
%li.separate-item
@@ -12,34 +12,35 @@
= icon('dashboard fw')
%span
Group
- - if current_user
- = nav_link(controller: [:group, :milestones]) do
- = link_to group_milestones_path(@group), title: 'Milestones', data: {placement: 'right'} do
- = icon('clock-o fw')
+ - if can?(current_user, :read_group, @group)
+ - if current_user
+ = nav_link(controller: [:group, :milestones]) do
+ = link_to group_milestones_path(@group), title: 'Milestones', data: {placement: 'right'} do
+ = icon('clock-o fw')
+ %span
+ Milestones
+ = nav_link(path: 'groups#issues') do
+ = link_to issues_group_path(@group), title: 'Issues', data: {placement: 'right'} do
+ = icon('exclamation-circle fw')
%span
- Milestones
- = nav_link(path: 'groups#issues') do
- = link_to issues_group_path(@group), title: 'Issues', data: {placement: 'right'} do
- = icon('exclamation-circle fw')
- %span
- Issues
- - if current_user
- %span.count= Issue.opened.of_group(@group).count
- = nav_link(path: 'groups#merge_requests') do
- = link_to merge_requests_group_path(@group), title: 'Merge Requests', data: {placement: 'right'} do
- = icon('tasks fw')
- %span
- Merge Requests
- - if current_user
- %span.count= MergeRequest.opened.of_group(@group).count
- = nav_link(controller: [:group_members]) do
- = link_to group_group_members_path(@group), title: 'Members', data: {placement: 'right'} do
- = icon('users fw')
- %span
- Members
- - if can?(current_user, :admin_group, @group)
- = nav_link(html_options: { class: "separate-item" }) do
- = link_to edit_group_path(@group), title: 'Settings', data: {placement: 'right'} do
- = icon ('cogs fw')
+ Issues
+ - if current_user
+ %span.count= Issue.opened.of_group(@group).count
+ = nav_link(path: 'groups#merge_requests') do
+ = link_to merge_requests_group_path(@group), title: 'Merge Requests', data: {placement: 'right'} do
+ = icon('tasks fw')
+ %span
+ Merge Requests
+ - if current_user
+ %span.count= MergeRequest.opened.of_group(@group).count
+ = nav_link(controller: [:group_members]) do
+ = link_to group_group_members_path(@group), title: 'Members', data: {placement: 'right'} do
+ = icon('users fw')
%span
- Settings
+ Members
+ - if can?(current_user, :admin_group, @group)
+ = nav_link(html_options: { class: "separate-item" }) do
+ = link_to edit_group_path(@group), title: 'Settings', data: {placement: 'right'} do
+ = icon ('cogs fw')
+ %span
+ Settings
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index 33fd5fcef6c..5a47b8e6db2 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -3,7 +3,7 @@
= link_to root_path, title: 'Back to dashboard', data: {placement: 'right'}, class: 'back-link' do
= icon('caret-square-o-left fw')
%span
- Back to Dashboard
+ Back to dashboard
%li.separate-item
@@ -11,7 +11,7 @@
= link_to profile_path, title: 'Profile', data: {placement: 'right'} do
= icon('user fw')
%span
- Profile
+ Profile Settings
= nav_link(controller: [:accounts, :two_factor_auths]) do
= link_to profile_account_path, title: 'Account', data: {placement: 'right'} do
= icon('gear fw')
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 5e7b902622b..a218ec7486c 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -4,13 +4,13 @@
= link_to group_path(@project.group), title: 'Back to group', data: {placement: 'right'}, class: 'back-link' do
= icon('caret-square-o-left fw')
%span
- Back to Group
+ Back to group
- else
= nav_link do
= link_to root_path, title: 'Back to dashboard', data: {placement: 'right'}, class: 'back-link' do
= icon('caret-square-o-left fw')
%span
- Back to Dashboard
+ Back to dashboard
%li.separate-item
@@ -26,28 +26,28 @@
Activity
- if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree)) do
- = link_to namespace_project_tree_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Files', class: 'shortcuts-tree', data: {placement: 'right'} do
+ = link_to project_files_path(@project), title: 'Files', class: 'shortcuts-tree', data: {placement: 'right'} do
= icon('files-o fw')
%span
Files
- if project_nav_tab? :commits
= nav_link(controller: %w(commit commits compare repositories tags branches)) do
- = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Commits', class: 'shortcuts-commits', data: {placement: 'right'} do
+ = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits', data: {placement: 'right'} do
= icon('history fw')
%span
Commits
- if project_nav_tab? :network
= nav_link(controller: %w(network)) do
- = link_to namespace_project_network_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Network', class: 'shortcuts-network', data: {placement: 'right'} do
+ = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network', data: {placement: 'right'} do
= icon('code-fork fw')
%span
Network
- if project_nav_tab? :graphs
= nav_link(controller: %w(graphs)) do
- = link_to namespace_project_graph_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Graphs', class: 'shortcuts-graphs', data: {placement: 'right'} do
+ = link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs', data: {placement: 'right'} do
= icon('area-chart fw')
%span
Graphs
@@ -76,6 +76,13 @@
Merge Requests
%span.count.merge_counter= @project.merge_requests.opened.count
+ - if @project.gitlab_ci?
+ = nav_link(controller: [:ci, :project]) do
+ = link_to ci_project_path(@project.gitlab_ci_project), title: 'Continuous Integration', data: {placement: 'right'} do
+ = icon('building fw')
+ %span
+ Continuous Integration
+
- if project_nav_tab? :settings
= nav_link(controller: [:project_members, :teams]) do
= link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab', data: {placement: 'right'} do
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
index 857fb199957..9279a846623 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -34,3 +34,29 @@
%span
Protected Branches
+ - if @project.gitlab_ci?
+ = nav_link(controller: :runners) do
+ = link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners', data: {placement: 'right'} do
+ = icon('cog fw')
+ %span
+ Runners
+ = nav_link(controller: :variables) do
+ = link_to namespace_project_variables_path(@project.namespace, @project) do
+ = icon('code fw')
+ %span
+ Variables
+ = nav_link path: 'triggers#index' do
+ = link_to namespace_project_triggers_path(@project.namespace, @project) do
+ = icon('retweet fw')
+ %span
+ Triggers
+ = nav_link path: 'ci_web_hooks#index' do
+ = link_to namespace_project_ci_web_hooks_path(@project.namespace, @project) do
+ = icon('link fw')
+ %span
+ CI Web Hooks
+ = nav_link path: 'ci_settings#edit' do
+ = link_to edit_namespace_project_ci_settings_path(@project.namespace, @project) do
+ = icon('building fw')
+ %span
+ CI Settings
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index ec209c38eed..2f7d7e86f56 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -42,5 +42,3 @@
- else
#{link_to "View it on GitLab", @target_url}
= email_action @target_url
- - if @project && !@disable_footer
- You're receiving this notification because you are a member of the #{link_to_unless @target_url, @project.name_with_namespace, namespace_project_url(@project.namespace, @project)} project team.
diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml
index 77d2ccbf762..dfa6cc5702e 100644
--- a/app/views/layouts/profile.html.haml
+++ b/app/views/layouts/profile.html.haml
@@ -1,5 +1,5 @@
- page_title "Profile Settings"
-- header_title "Profile Settings", profile_path
+- header_title "Profile Settings", profile_path unless header_title
- sidebar "profile"
= render template: "layouts/application"
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index 44afa33dfe5..abf73bcc709 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -1,12 +1,13 @@
- page_title @project.name_with_namespace
-- header_title project_title(@project)
-- sidebar "project" unless sidebar
+- header_title project_title(@project) unless header_title
+- sidebar "project" unless sidebar
- content_for :scripts_body_top do
+ - project = @target_project || @project
- if current_user
:javascript
- window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
- window.markdown_preview_path = "#{markdown_preview_namespace_project_path(@project.namespace, @project)}";
+ window.project_uploads_path = "#{namespace_project_uploads_path project.namespace,project}";
+ window.markdown_preview_path = "#{markdown_preview_namespace_project_path(project.namespace, project)}";
- content_for :scripts_body do
= render "layouts/init_auto_complete" if current_user
diff --git a/app/views/layouts/project_settings.html.haml b/app/views/layouts/project_settings.html.haml
index 43401668334..59ce38f67bb 100644
--- a/app/views/layouts/project_settings.html.haml
+++ b/app/views/layouts/project_settings.html.haml
@@ -1,4 +1,5 @@
- page_title "Settings"
+- header_title project_title(@project, "Settings", edit_project_path(@project))
- sidebar "project_settings"
= render template: "layouts/project"
diff --git a/app/views/layouts/snippets.html.haml b/app/views/layouts/snippets.html.haml
index b77fe09fc2a..02ca3ee7a28 100644
--- a/app/views/layouts/snippets.html.haml
+++ b/app/views/layouts/snippets.html.haml
@@ -1,8 +1,3 @@
-- page_title 'Snippets'
-- if current_user
- - header_title "Dashboard", root_path
-- else
- - header_title 'Snippets', snippets_path
-- sidebar "dashboard"
+- header_title "Snippets", snippets_path
= render template: "layouts/application"
diff --git a/app/views/notify/merged_merge_request_email.text.haml b/app/views/notify/merged_merge_request_email.text.haml
index 9db75bdb19e..34dbc60e19b 100644
--- a/app/views/notify/merged_merge_request_email.text.haml
+++ b/app/views/notify/merged_merge_request_email.text.haml
@@ -1,6 +1,6 @@
= "Merge Request ##{@merge_request.iid} was merged"
-Merge Request Url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
+Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/new_user_email.html.haml b/app/views/notify/new_user_email.html.haml
index 4feacdaacff..6b9b42dcf37 100644
--- a/app/views/notify/new_user_email.html.haml
+++ b/app/views/notify/new_user_email.html.haml
@@ -13,4 +13,4 @@
%p
= link_to "Click here to set your password", edit_password_url(@user, reset_password_token: @token)
%p
- = reset_token_expire_message
+ = raw reset_token_expire_message
diff --git a/app/views/notify/project_was_moved_email.html.haml b/app/views/notify/project_was_moved_email.html.haml
index 3cd759f1f57..87b3ff7f0b3 100644
--- a/app/views/notify/project_was_moved_email.html.haml
+++ b/app/views/notify/project_was_moved_email.html.haml
@@ -1,5 +1,5 @@
%p
- Project was moved to another location
+ Project #{@old_path_with_namespace} was moved to another location
%p
The project is now located under
= link_to namespace_project_url(@project.namespace, @project) do
diff --git a/app/views/notify/project_was_moved_email.text.erb b/app/views/notify/project_was_moved_email.text.erb
index b3f18b35a4d..d8a23dabf49 100644
--- a/app/views/notify/project_was_moved_email.text.erb
+++ b/app/views/notify/project_was_moved_email.text.erb
@@ -1,4 +1,4 @@
-Project was moved to another location
+Project #{@old_path_with_namespace} was moved to another location
The project is now located under
<%= namespace_project_url(@project.namespace, @project) %>
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 767fe2e0e9a..cd7b1b0fe03 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -1,9 +1,7 @@
- page_title "Account"
-%h3.page-title
- = page_title
-%p.light
- Change your username and basic account settings.
-%hr
+- header_title page_title, profile_account_path
+- @blank_container = true
+
- if current_user.ldap_user?
.alert.alert-info
Some options are unavailable for LDAP accounts
@@ -69,7 +67,7 @@
- button_based_providers.each do |provider|
.btn-group
= link_to provider_image_tag(provider), user_omniauth_authorize_path(provider), method: :post, class: "btn btn-lg #{'active' if auth_active?(provider)}", "data-no-turbolink" => "true"
-
+
- if auth_active?(provider)
= link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'btn btn-lg' do
= icon('close')
diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml
index 3a3e6e1b1c4..2342936a5d5 100644
--- a/app/views/profiles/applications.html.haml
+++ b/app/views/profiles/applications.html.haml
@@ -1,13 +1,12 @@
- page_title "Applications"
-%h3.page-title
- = page_title
-%p.light
+- header_title page_title, applications_profile_path
+
+.gray-content-block.top-block
- if user_oauth_applications?
- Manage applications that can use GitLab as an OAuth provider,
+ Manage applications that can use GitLab as an OAuth provider,
and applications that you've authorized to use your account.
- else
Manage applications that you've authorized to use your account.
-%hr
- if user_oauth_applications?
.oauth-applications
diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml
index 698d6037428..8fdba45b193 100644
--- a/app/views/profiles/audit_log.html.haml
+++ b/app/views/profiles/audit_log.html.haml
@@ -1,5 +1,8 @@
- page_title "Audit Log"
-%h3.page-title Audit Log
-%p.light History of authentications
+- header_title page_title, audit_log_profile_path
-= render 'event_table', events: @events \ No newline at end of file
+.gray-content-block.top-block
+ History of authentications
+
+.prepend-top-default
+= render 'event_table', events: @events
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 66812872c41..1d140347a5f 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -1,12 +1,10 @@
- page_title "Emails"
-%h3.page-title
- = page_title
-%p.light
- Control emails linked to your account
-%hr
+- header_title page_title, profile_emails_path
+.gray-content-block.top-block
+ Control emails linked to your account
-%ul
+%ul.prepend-top-default
%li
Your
%b Primary Email
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index 06655f7ba3a..14adba1c797 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -1,11 +1,12 @@
- page_title "SSH Keys"
-%h3.page-title
- = page_title
+- header_title page_title, profile_keys_path
+
+.gray-content-block.top-block
.pull-right
= link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new"
-%p.light
- Before you can add an SSH key you need to
- = link_to "generate it.", help_page_path("ssh", "README")
-%hr
+ .oneline
+ Before you can add an SSH key you need to
+ = link_to "generate it.", help_page_path("ssh", "README")
+.prepend-top-default
= render 'key_table'
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index db7fa2eabe3..8eebd96b674 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -1,10 +1,10 @@
- page_title "Notifications"
-%h3.page-title
- = page_title
-%p.light
+- header_title page_title, profile_notifications_path
+
+.gray-content-block.top-block
These are your global notification settings.
-%hr
+.prepend-top-default
= form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications form-horizontal global-notifications-form' } do |f|
-if @user.errors.any?
%div.alert.alert-danger
@@ -33,7 +33,7 @@
= f.label :notification_level, value: Notification::N_MENTION do
= f.radio_button :notification_level, Notification::N_MENTION
.level-title
- Mention
+ On Mention
%p You will receive notifications only for comments in which you were @mentioned
.radio
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index 399ae98adf9..fab7c45c9b2 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -1,13 +1,13 @@
- page_title "Password"
-%h3.page-title
- = page_title
-%p.light
+- header_title page_title, edit_profile_password_path
+
+.gray-content-block.top-block
- if @user.password_automatically_set?
Set your password.
- else
Change your password or recover your current one.
-%hr
-.update-password
+
+.update-password.prepend-top-default
= form_for @user, url: profile_password_path, method: :put, html: { class: 'form-horizontal' } do |f|
%div
%p.slead
diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml
index 9c6204963e0..d165f758c81 100644
--- a/app/views/profiles/passwords/new.html.haml
+++ b/app/views/profiles/passwords/new.html.haml
@@ -12,7 +12,7 @@
%ul
- @user.errors.full_messages.each do |msg|
%li= msg
-
+
- unless @user.password_automatically_set?
.form-group
= f.label :current_password, class: 'control-label'
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index aa0361a0a1b..01e285a8dfa 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -1,11 +1,11 @@
- page_title 'Preferences'
-%h3.page-title
- = page_title
-%p.light
+- header_title page_title, profile_preferences_path
+- @blank_container = true
+
+.alert.alert-help
These settings allow you to customize the appearance and behavior of the site.
They are saved with your account and will persist to any device you use to
access the site.
-%hr
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'js-preferences-form form-horizontal'} do |f|
.panel.panel-default.application-theme
@@ -33,6 +33,13 @@
Behavior
.panel-body
.form-group
+ = f.label :layout, class: 'control-label' do
+ Layout width
+ .col-sm-10
+ = f.select :layout, layout_choices, {}, class: 'form-control'
+ .help-block
+ Choose between fixed (max. 1200px) and fluid (100%) application layout
+ .form-group
= f.label :dashboard, class: 'control-label' do
Default Dashboard
= link_to('(?)', help_page_path('profile', 'preferences') + '#default-dashboard', target: '_blank')
diff --git a/app/views/profiles/preferences/update.js.erb b/app/views/profiles/preferences/update.js.erb
index 6c4b0ce757d..4433cab7782 100644
--- a/app/views/profiles/preferences/update.js.erb
+++ b/app/views/profiles/preferences/update.js.erb
@@ -2,6 +2,13 @@
$('body').removeClass('<%= Gitlab::Themes.body_classes %>')
$('body').addClass('<%= user_application_theme %>')
+// Toggle container-fluid class
+if ('<%= current_user.layout %>' === 'fluid') {
+ $('.content-wrapper').find('.container-fluid').removeClass('container-limited')
+} else {
+ $('.content-wrapper').find('.container-fluid').addClass('container-limited')
+}
+
// Re-enable the "Save" button
$('input[type=submit]').enable()
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index c519e52e596..47412e2ef0c 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -1,14 +1,9 @@
-- page_title "Profile"
-%h3.page-title
- = page_title
-%p.light
+.gray-content-block.top-block
This information will appear on your profile.
- if current_user.ldap_user?
Some options are unavailable for LDAP accounts
-%hr
-
-
+.prepend-top-default
= form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit_user form-horizontal" }, authenticity_token: true do |f|
-if @user.errors.any?
%div.alert.alert-danger
diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml
index ee02b7f6a6c..1261f6254d7 100644
--- a/app/views/projects/_activity.html.haml
+++ b/app/views/projects/_activity.html.haml
@@ -1,5 +1,5 @@
= render 'projects/last_push'
-.hidden-xs
+.gray-content-block.activity-filter-block
- if current_user
%ul.nav.nav-pills.event_filter.pull-right
%li
@@ -7,7 +7,6 @@
%i.fa.fa-rss
= render 'shared/event_filter'
- %hr
.content_list{:"data-href" => activity_project_path(@project)}
= spinner
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index b93036e78e6..8c0980369fd 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -7,24 +7,28 @@
- if @project.description.present?
= markdown(@project.description, pipeline: :description)
-
- .project-repo-buttons
- = render 'projects/buttons/star'
-
- - unless empty_repo
- = render 'projects/buttons/fork'
-
- - if forked_from_project = @project.forked_from_project
- = link_to project_path(forked_from_project), class: 'btn' do
- = icon("code-fork fw")
- Forked from
+ - if forked_from_project = @project.forked_from_project
+ %p
+ Forked from
+ = link_to project_path(forked_from_project) do
= forked_from_project.namespace.try(:name)
- - if can? current_user, :download_code, @project
- = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
- = icon('download fw')
- Download
- = render 'projects/buttons/dropdown'
- = render "shared/clone_panel"
+ .project-repo-buttons
+ .split-one
+ = render 'projects/buttons/star'
+
+ - unless empty_repo
+ = render 'projects/buttons/fork'
+
+ = render "shared/clone_panel"
+ .split-repo-buttons
+ - unless empty_repo
+ - if can? current_user, :download_code, @project
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
+ = icon('download fw')
+
+ = render 'projects/buttons/dropdown'
+ = render 'projects/buttons/notifications'
+
diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml
index 30622d8a910..f0a3e416db7 100644
--- a/app/views/projects/_last_push.html.haml
+++ b/app/views/projects/_last_push.html.haml
@@ -1,14 +1,15 @@
- if event = last_push_event
- if show_last_push_widget?(event)
- .hidden-xs.center
- .slead
- %span You pushed to
- = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do
- %strong= event.ref_name
- branch
- #{time_ago_with_tooltip(event.created_at)}
- %div
- = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do
- Create Merge Request
- %hr
+ .gray-content-block.top-block.clear-block.hidden-xs
+ .event-last-push
+ .event-last-push-text
+ %span You pushed to
+ = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do
+ %strong= event.ref_name
+ branch
+ #{time_ago_with_tooltip(event.created_at)}
+
+ .pull-right
+ = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do
+ Create Merge Request
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index b7bca6dae09..507757f6a2b 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -1,6 +1,6 @@
.md-area
.md-header.clearfix
- %ul.nav.nav-tabs
+ %ul.center-top-menu
%li.active
= link_to '#md-write-holder', class: 'js-md-write-button', tabindex: '-1' do
Write
@@ -14,7 +14,7 @@
You are about to add
%strong
%span.js-referenced-users-count 0
- people
+ people
to the discussion. Proceed with caution.
%div
diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml
index 5038edb95ed..5bc1999ec9d 100644
--- a/app/views/projects/_readme.html.haml
+++ b/app/views/projects/_readme.html.haml
@@ -5,7 +5,7 @@
&nbsp;
- if can?(current_user, :push_code, @project)
= link_to namespace_project_edit_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)), class: 'light' do
- %i.fa.fa-pencil
+ %i.fa-align.fa.fa-pencil
.wiki
= cache(readme_cache_key) do
= render_readme(readme)
diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml
index 65674913bb0..555ed76426d 100644
--- a/app/views/projects/activity.html.haml
+++ b/app/views/projects/activity.html.haml
@@ -1 +1,4 @@
+- page_title "Activity"
+- header_title project_title(@project, "Activity", activity_project_path(@project))
+
= render 'projects/activity'
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index c1ec42aefca..6518c4173e1 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -1,4 +1,6 @@
- page_title "Blame", @blob.path, @ref
+- header_title project_title(@project, "Files", project_files_path(@project))
+
%h3.page-title Blame view
#tree-holder.tree-holder
diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml
index 13f8271b979..373b3a0c5b0 100644
--- a/app/views/projects/blob/_actions.html.haml
+++ b/app/views/projects/blob/_actions.html.haml
@@ -17,6 +17,6 @@
tree_join(@commit.sha, @path)), class: 'btn btn-sm'
- if allowed_tree_edit?
- = button_tag class: 'remove-blob btn btn-sm btn-remove',
- 'data-toggle' => 'modal', 'data-target' => '#modal-remove-blob' do
- Remove
+ .btn-group{ role: "group" }
+ %button.btn.btn-default{ 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } Replace
+ %button.btn.btn-remove{ 'data-target' => '#modal-remove-blob', 'data-toggle' => 'modal' } Remove
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index 65c3ab10e02..b4c7d8b9b71 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -15,7 +15,7 @@
- else
= link_to title, '#'
-%ul.blob-commit-info.well.hidden-xs
+%ul.blob-commit-info.hidden-xs
- blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
= render blob_commit, project: @project
diff --git a/app/views/projects/blob/_header_title.html.haml b/app/views/projects/blob/_header_title.html.haml
new file mode 100644
index 00000000000..78c5ef20a5f
--- /dev/null
+++ b/app/views/projects/blob/_header_title.html.haml
@@ -0,0 +1 @@
+- header_title project_title(@project, "Files", project_files_path(@project))
diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml
new file mode 100644
index 00000000000..1a1df127703
--- /dev/null
+++ b/app/views/projects/blob/_upload.html.haml
@@ -0,0 +1,28 @@
+#modal-upload-blob.modal
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %a.close{href: "#", "data-dismiss" => "modal"} ×
+ %h3.page-title #{title}
+ %p.light
+ From branch
+ %strong= @ref
+ .modal-body
+ = form_tag form_path, method: method, class: 'blob-file-upload-form-js form-horizontal' do
+ .dropzone
+ .dropzone-previews.blob-upload-dropzone-previews
+ %p.dz-message.light
+ Attach a file by drag &amp; drop or
+ = link_to 'click to upload', '#', class: "markdown-selector"
+ %br
+ .dropzone-alerts{class: "alert alert-danger data", style: "display:none"}
+ = render 'shared/commit_message_container', params: params,
+ placeholder: placeholder
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ = button_tag button_title, class: 'btn btn-small btn-primary btn-upload-file', id: 'submit-all'
+ = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
+
+:coffeescript
+ disableButtonIfEmptyField $('.blob-file-upload-form-js').find('#commit_message'), '.btn-upload-file'
+ new BlobFileDropzone($('.blob-file-upload-form-js'), '#{method}')
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index a12cd660fc1..a811adc5094 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -1,6 +1,8 @@
- page_title "Edit", @blob.path, @ref
+= render "header_title"
+
.file-editor
- %ul.nav.nav-tabs.js-edit-mode
+ %ul.center-top-menu.no-bottom.js-edit-mode
%li.active
= link_to '#editor' do
%i.fa.fa-edit
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index 7c2a4fece94..1950586b112 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -1,5 +1,14 @@
-- page_title "New File", @ref
-%h3.page-title New file
+- page_title "New File", @path.presence, @ref
+= render "header_title"
+
+.gray-content-block.top-block
+ Create a new file or
+ = link_to 'upload', '#modal-upload-blob',
+ { class: 'upload-link', 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal'}
+ an existing one
+
+= render 'projects/blob/upload', title: 'Upload', placeholder: 'Upload new file', button_title: 'Upload file', form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post
+
.file-editor
= form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal form-new-file js-requires-input') do
= render 'projects/blob/editor', ref: @ref
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index bd2fc43633c..fa4be4a1bc4 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -1,4 +1,5 @@
- page_title @blob.path, @ref
+= render "header_title"
= render 'projects/last_push'
@@ -10,3 +11,8 @@
- if allowed_tree_edit?
= render 'projects/blob/remove'
+
+ - title = "Replace #{@blob.name}"
+ = render 'projects/blob/upload', title: title, placeholder: title,
+ button_title: 'Replace file', form_path: namespace_project_update_blob_path(@project.namespace, @project, @id),
+ method: :put
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index a693c4b282f..cc0ec9483d2 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -1,20 +1,20 @@
- commit = @repository.commit(branch.target)
%li(class="js-branch-#{branch.name}")
- %h4
+ %div
= link_to namespace_project_tree_path(@project.namespace, @project, branch.name) do
%strong.str-truncated= branch.name
+ &nbsp;
- if branch.name == @repository.root_ref
- %span.label.label-info default
+ %span.label.label-primary default
- elsif @repository.merged_to_root_ref? branch.name
- %span.label.label-primary.has_tooltip(title="Merged into #{@repository.root_ref}")
- %i.fa.fa-check
+ %span.label.label-info.has_tooltip(title="Merged into #{@repository.root_ref}")
merged
- if @project.protected_branch? branch.name
%span.label.label-success
%i.fa.fa-lock
protected
- .pull-right
+ .controls.hidden-xs
- if create_mr_button?(@repository.root_ref, branch.name)
= link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-grouped btn-xs' do
= icon('plus')
@@ -30,8 +30,7 @@
= icon("trash-o")
- if commit
- %ul.list-unstyled
- = render 'projects/commits/inline_commit', commit: commit, project: @project
+ = render 'projects/branches/commit', commit: commit, project: @project
- else
%p
Cant find HEAD commit for this branch
diff --git a/app/views/projects/branches/_commit.html.haml b/app/views/projects/branches/_commit.html.haml
new file mode 100644
index 00000000000..68326e65d85
--- /dev/null
+++ b/app/views/projects/branches/_commit.html.haml
@@ -0,0 +1,7 @@
+.branch-commit.light
+ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
+ &middot;
+ %span.str-truncated
+ = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
+ &middot;
+ #{time_ago_with_tooltip(commit.committed_date)}
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 80acc937908..03ade02a0c8 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -1,7 +1,7 @@
- page_title "Branches"
+= render "projects/commits/header_title"
= render "projects/commits/head"
-%h3.page-title
- Branches
+.gray-content-block
.pull-right
- if can? current_user, :push_code, @project
= link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
@@ -24,9 +24,10 @@
= sort_title_recently_updated
= link_to namespace_project_branches_path(sort: 'last_updated') do
= sort_title_oldest_updated
-%hr
+ .oneline
+ Protected branches can be managed in project settings
- unless @branches.empty?
- %ul.bordered-list.top-list.all-branches
+ %ul.content-list.all-branches
- @branches.each do |branch|
= render "projects/branches/branch", branch: branch
= paginate @branches, theme: 'gitlab'
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index 29e82b93883..f5577042ca4 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -1,4 +1,6 @@
- page_title "New Branch"
+= render "projects/commits/header_title"
+
- if @error
.alert.alert-danger
%button{ type: "button", class: "close", "data-dismiss" => "alert"} &times;
diff --git a/app/views/projects/builds/_build.html.haml b/app/views/projects/builds/_build.html.haml
new file mode 100644
index 00000000000..21c543b38dd
--- /dev/null
+++ b/app/views/projects/builds/_build.html.haml
@@ -0,0 +1,50 @@
+- gl_project = build.project.gl_project
+%tr.build
+ %td.status
+ = ci_status_with_icon(build.status)
+
+ %td.build-link
+ = link_to namespace_project_build_path(gl_project.namespace, gl_project, build) do
+ %strong Build ##{build.id}
+
+ - if defined?(ref)
+ %td
+ = build.ref
+
+ %td
+ = build.stage
+
+ %td
+ = build.name
+ .pull-right
+ - if build.tags.any?
+ - build.tag_list.each do |tag|
+ %span.label.label-primary
+ = tag
+ - if build.trigger_request
+ %span.label.label-info triggered
+ - if build.allow_failure
+ %span.label.label-danger allowed to fail
+
+ %td.duration
+ - if build.duration
+ #{duration_in_words(build.finished_at, build.started_at)}
+
+ %td.timestamp
+ - if build.finished_at
+ %span #{time_ago_in_words build.finished_at} ago
+
+ - if build.project.coverage_enabled?
+ %td.coverage
+ - if build.coverage
+ #{build.coverage}%
+
+ %td
+ - if defined?(controls) && current_user && can?(current_user, :manage_builds, gl_project)
+ .pull-right
+ - if build.active?
+ = link_to cancel_ci_project_build_path(build.project, build, return_to: request.original_url), title: 'Cancel build' do
+ %i.fa.fa-remove.cred
+ - elsif build.commands.present?
+ = link_to retry_ci_project_build_path(build.project, build, return_to: request.original_url), method: :post, title: 'Retry build' do
+ %i.fa.fa-repeat
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
new file mode 100644
index 00000000000..93cd4dcfd93
--- /dev/null
+++ b/app/views/projects/builds/show.html.haml
@@ -0,0 +1,159 @@
+.build-page
+ .gray-content-block
+ Build for commit
+ %strong.monospace
+ = link_to @build.commit.short_sha, ci_status_path(@build.commit)
+ from
+ %code #{@build.ref}
+
+ #up-build-trace
+ - if @commit.matrix_for_ref?(@build.ref)
+ %ul.center-top-menu.build-top-menu
+ - @commit.builds_without_retry_for_ref(@build.ref).each do |build|
+ %li{class: ('active' if build == @build) }
+ = link_to namespace_project_build_path(@project.namespace, @project, build) do
+ = ci_icon_for_status(build.status)
+ %span
+ - if build.name
+ = build.name
+ - else
+ = build.id
+
+
+ - unless @commit.builds_without_retry_for_ref(@build.ref).include?(@build)
+ %li.active
+ %a
+ Build ##{@build.id}
+ &middot;
+ %i.fa.fa-warning-sign
+ This build was retried.
+
+ .gray-content-block.second-block
+ .build-head
+ .clearfix
+ = ci_status_with_icon(@build.status)
+ - if @build.duration
+ %span
+ %i.fa.fa-time
+ #{duration_in_words(@build.finished_at, @build.started_at)}
+ .pull-right
+ = @build.updated_at.stamp('19:00 Aug 27')
+
+ .row.prepend-top-default
+ .col-md-9
+ .clearfix
+ - if @build.active?
+ .autoscroll-container
+ %button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll
+ .clearfix
+ .scroll-controls
+ = link_to '#up-build-trace', class: 'btn' do
+ %i.fa.fa-angle-up
+ = link_to '#down-build-trace', class: 'btn' do
+ %i.fa.fa-angle-down
+
+ %pre.trace#build-trace
+ %code.bash
+ = preserve do
+ = raw @build.trace_html
+ %div#down-build-trace
+
+ .col-md-3
+ - if @build.coverage
+ .build-widget
+ %h4.title
+ Test coverage
+ %h1 #{@build.coverage}%
+
+
+ .build-widget
+ %h4.title
+ Build
+ - if current_user && can?(current_user, :manage_builds, @project)
+ .pull-right
+ - if @build.active?
+ = link_to "Cancel", cancel_ci_project_build_path(@ci_project, @build), class: 'btn btn-sm btn-danger'
+ - elsif @build.commands.present?
+ = link_to "Retry", retry_ci_project_build_path(@ci_project, @build), class: 'btn btn-sm btn-primary', method: :post
+
+ - if @build.duration
+ %p
+ %span.attr-name Duration:
+ #{duration_in_words(@build.finished_at, @build.started_at)}
+ %p
+ %span.attr-name Created:
+ #{time_ago_in_words(@build.created_at)} ago
+ - if @build.finished_at
+ %p
+ %span.attr-name Finished:
+ #{time_ago_in_words(@build.finished_at)} ago
+ %p
+ %span.attr-name Runner:
+ - if @build.runner && current_user && current_user.admin
+ \#{link_to "##{@build.runner.id}", ci_admin_runner_path(@build.runner.id)}
+ - elsif @build.runner
+ \##{@build.runner.id}
+
+ - if @build.trigger_request
+ .build-widget
+ %h4.title
+ Trigger
+
+ %p
+ %span.attr-name Token:
+ #{@build.trigger_request.trigger.short_token}
+
+ - if @build.trigger_request.variables
+ %p
+ %span.attr-name Variables:
+
+ %code
+ - @build.trigger_request.variables.each do |key, value|
+ #{key}=#{value}
+
+ .build-widget
+ %h4.title
+ Commit
+ .pull-right
+ %small #{build_commit_link @build}
+ %p
+ %span.attr-name Branch:
+ #{build_ref_link @build}
+ %p
+ %span.attr-name Author:
+ #{@build.commit.git_author_name}
+ %p
+ %span.attr-name Message:
+ #{@build.commit.git_commit_message}
+
+ - if @build.tags.any?
+ .build-widget
+ %h4.title
+ Tags
+ - @build.tag_list.each do |tag|
+ %span.label.label-primary
+ = tag
+
+ - if @builds.present?
+ .build-widget
+ %h4.title #{pluralize(@builds.count, "other build")} for #{@build.short_sha}:
+ %table.table.builds
+ - @builds.each_with_index do |build, i|
+ %tr.build
+ %td
+ = ci_icon_for_status(build.status)
+ %td
+ = link_to namespace_project_build_path(@project.namespace, @project, @build) do
+ - if build.name
+ = build.name
+ - else
+ %span ##{build.id}
+
+ %td.status= build.status
+
+
+ = paginate @builds
+
+
+ :javascript
+ new CiBuild("#{namespace_project_build_path(@project.namespace, @project, @build)}", "#{@build.status}")
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index bc7625e8989..4580c912692 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -1,6 +1,6 @@
- if current_user
%span.dropdown
- %a.dropdown-toggle.btn.btn-new{href: '#', "data-toggle" => "dropdown"}
+ %a.dropdown-new.btn.btn-new{href: '#', "data-toggle" => "dropdown"}
= icon('plus')
%ul.dropdown-menu.dropdown-menu-right.project-home-dropdown
- if can?(current_user, :create_issue, @project)
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index 854c154824d..8f2f631eb7d 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -8,6 +8,5 @@
- else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn' do
= icon('code-fork fw')
- Fork
%span.count
= @project.forks_count
diff --git a/app/views/projects/buttons/_notifications.html.haml b/app/views/projects/buttons/_notifications.html.haml
new file mode 100644
index 00000000000..4b69a6d7a6f
--- /dev/null
+++ b/app/views/projects/buttons/_notifications.html.haml
@@ -0,0 +1,14 @@
+- return unless @membership
+
+= form_tag profile_notifications_path, method: :put, remote: true, class: 'inline-form', id: 'notification-form' do
+ = hidden_field_tag :notification_type, 'project'
+ = hidden_field_tag :notification_id, @membership.id
+ = hidden_field_tag :notification_level
+ %span.dropdown
+ %a.dropdown-new.btn.btn-new#notifications-button{href: '#', "data-toggle" => "dropdown"}
+ = icon('bell')
+ = notification_label(@membership)
+ = icon('angle-down')
+ %ul.dropdown-menu.dropdown-menu-right.project-home-dropdown
+ - Notification.project_notification_levels.each do |level|
+ = notification_list_item(level, @membership)
diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml
index 5d7df5ae099..3501dddefbe 100644
--- a/app/views/projects/buttons/_star.html.haml
+++ b/app/views/projects/buttons/_star.html.haml
@@ -1,10 +1,6 @@
- if current_user
= link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star', method: :post, remote: true do
= icon('star fw')
- - if current_user.starred?(@project)
- Unstar
- - else
- Star
%span.count
= @project.star_count
@@ -17,6 +13,5 @@
- else
= link_to new_user_session_path, class: 'btn has_tooltip star-btn', title: 'You must sign in to star a project' do
= icon('star fw')
- Star
%span.count
= @project.star_count
diff --git a/app/views/projects/ci_settings/_form.html.haml b/app/views/projects/ci_settings/_form.html.haml
new file mode 100644
index 00000000000..9f891f557a9
--- /dev/null
+++ b/app/views/projects/ci_settings/_form.html.haml
@@ -0,0 +1,103 @@
+%h3.page-title
+ CI settings
+%hr
+.bs-callout.help-callout
+ %p
+ If you want to test your .gitlab-ci.yml, you can use special tool - #{link_to "Lint", ci_lint_path}
+ %p
+ Edit your
+ #{link_to ".gitlab-ci.yml using web-editor", yaml_web_editor_link(@ci_project)}
+
+= nested_form_for @ci_project, url: namespace_project_ci_settings_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f|
+ - if @ci_project.errors.any?
+ #error_explanation
+ %p.lead= "#{pluralize(@ci_project.errors.count, "error")} prohibited this project from being saved:"
+ .alert.alert-error
+ %ul
+ - @ci_project.errors.full_messages.each do |msg|
+ %li= msg
+
+ %fieldset
+ %legend Build settings
+ .form-group
+ = label_tag nil, class: 'control-label' do
+ Get code
+ .col-sm-10
+ %p Get recent application code using the following command:
+ .radio
+ = label_tag do
+ = f.radio_button :allow_git_fetch, 'false'
+ %strong git clone
+ .light Slower but makes sure you have a clean dir before every build
+ .radio
+ = label_tag do
+ = f.radio_button :allow_git_fetch, 'true'
+ %strong git fetch
+ .light Faster
+ .form-group
+ = f.label :timeout_in_minutes, 'Timeout', class: 'control-label'
+ .col-sm-10
+ = f.number_field :timeout_in_minutes, class: 'form-control', min: '0'
+ .light per build in minutes
+
+
+ %fieldset
+ %legend Build Schedule
+ .form-group
+ = f.label :always_build, 'Schedule build', class: 'control-label'
+ .col-sm-10
+ .checkbox
+ = f.label :always_build do
+ = f.check_box :always_build
+ %span.light Repeat last build after X hours if no builds
+ .form-group
+ = f.label :polling_interval, "Build interval", class: 'control-label'
+ .col-sm-10
+ = f.number_field :polling_interval, placeholder: '5', min: '0', class: 'form-control'
+ .light In hours
+
+ %fieldset
+ %legend Project settings
+ .form-group
+ = f.label :default_ref, "Make tabs for the following branches", class: 'control-label'
+ .col-sm-10
+ = f.text_field :default_ref, class: 'form-control', placeholder: 'master, stable'
+ .light You will be able to filter builds by the following branches
+ .form-group
+ = f.label :public, 'Public mode', class: 'control-label'
+ .col-sm-10
+ .checkbox
+ = f.label :public do
+ = f.check_box :public
+ %span.light Anyone can see project and builds
+ .form-group
+ = f.label :coverage_regex, "Test coverage parsing", class: 'control-label'
+ .col-sm-10
+ .input-group
+ %span.input-group-addon /
+ = f.text_field :coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered'
+ %span.input-group-addon /
+ .light We will use this regular expression to find test coverage output in build trace. Leave blank if you want to disable this feature
+ .bs-callout.bs-callout-info
+ %p Below are examples of regex for existing tools:
+ %ul
+ %li
+ Simplecov (Ruby) -
+ %code \(\d+.\d+\%\) covered
+ %li
+ pytest-cov (Python) -
+ %code \d+\%$
+
+
+
+ %fieldset
+ %legend Advanced settings
+ .form-group
+ = f.label :token, "CI token", class: 'control-label'
+ .col-sm-10
+ = f.text_field :token, class: 'form-control', placeholder: 'xEeFCaDAB89'
+
+ .form-actions
+ = f.submit 'Save changes', class: 'btn btn-save'
+ - unless @ci_project.new_record?
+ = link_to 'Remove Project', ci_project_path(@ci_project), method: :delete, data: { confirm: 'Project will be removed. Are you sure?' }, class: 'btn btn-danger pull-right'
diff --git a/app/views/projects/ci_settings/edit.html.haml b/app/views/projects/ci_settings/edit.html.haml
new file mode 100644
index 00000000000..e9040fe4337
--- /dev/null
+++ b/app/views/projects/ci_settings/edit.html.haml
@@ -0,0 +1,21 @@
+- if @ci_project.generated_yaml_config
+ %p.alert.alert-danger
+ CI Jobs are deprecated now, you can #{link_to "download", dumped_yaml_ci_project_path(@ci_project)}
+ or
+ %a.preview-yml{:href => "#yaml-content", "data-toggle" => "modal"} preview
+ yaml file which is based on your old jobs.
+ Put this file to the root of your project and name it .gitlab-ci.yml
+
+= render 'form'
+
+- if @ci_project.generated_yaml_config
+ #yaml-content.modal.fade{"aria-hidden" => "true", "aria-labelledby" => ".gitlab-ci.yml", :role => "dialog", :tabindex => "-1"}
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %button.close{"aria-hidden" => "true", "data-dismiss" => "modal", :type => "button"} ×
+ %h4.modal-title Content of .gitlab-ci.yml
+ .modal-body
+ = text_area_tag :yaml, @ci_project.generated_yaml_config, size: "70x25", class: "form-control"
+ .modal-footer
+ %button.btn.btn-default{"data-dismiss" => "modal", :type => "button"} Close
diff --git a/app/views/projects/ci_web_hooks/index.html.haml b/app/views/projects/ci_web_hooks/index.html.haml
new file mode 100644
index 00000000000..6aebd7cfc4d
--- /dev/null
+++ b/app/views/projects/ci_web_hooks/index.html.haml
@@ -0,0 +1,92 @@
+%h3.page-title
+ CI Web hooks
+
+%p.light
+ Web Hooks can be used for binding events when build completed.
+
+%hr.clearfix
+
+= form_for @web_hook, url: namespace_project_ci_web_hooks_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f|
+ -if @web_hook.errors.any?
+ .alert.alert-danger
+ - @web_hook.errors.full_messages.each do |msg|
+ %p= msg
+ .form-group
+ = f.label :url, "URL", class: 'control-label'
+ .col-sm-10
+ = f.text_field :url, class: "form-control", placeholder: 'http://example.com/trigger-ci.json'
+ .form-actions
+ = f.submit "Add Web Hook", class: "btn btn-create"
+
+-if @web_hooks.any?
+ %h4 Activated web hooks (#{@web_hooks.count})
+ %table.table
+ - @web_hooks.each do |hook|
+ %tr
+ %td
+ .clearfix
+ %span.monospace= hook.url
+ %td
+ .pull-right
+ - if @ci_project.commits.any?
+ = link_to 'Test Hook', test_namespace_project_ci_web_hook_path(@project.namespace, @project, hook), class: "btn btn-sm btn-grouped"
+ = link_to 'Remove', namespace_project_ci_web_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
+
+%h4 Web Hook data example
+
+:erb
+ <pre>
+ <code>
+ {
+ "build_id": 2,
+ "build_name":"rspec_linux"
+ "build_status": "failed",
+ "build_started_at": "2014-05-05T18:01:02.563Z",
+ "build_finished_at": "2014-05-05T18:01:07.611Z",
+ "project_id": 1,
+ "project_name": "Brightbox \/ Brightbox Cli",
+ "gitlab_url": "http:\/\/localhost:3000\/brightbox\/brightbox-cli",
+ "ref": "master",
+ "sha": "a26cf5de9ed9827746d4970872376b10d9325f40",
+ "before_sha": "34f57f6ba3ed0c21c5e361bbb041c3591411176c",
+ "push_data": {
+ "before": "34f57f6ba3ed0c21c5e361bbb041c3591411176c",
+ "after": "a26cf5de9ed9827746d4970872376b10d9325f40",
+ "ref": "refs\/heads\/master",
+ "user_id": 1,
+ "user_name": "Administrator",
+ "project_id": 5,
+ "repository": {
+ "name": "Brightbox Cli",
+ "url": "dzaporozhets@localhost:brightbox\/brightbox-cli.git",
+ "description": "Voluptatibus quae error consectetur voluptas dolores vel excepturi possimus.",
+ "homepage": "http:\/\/localhost:3000\/brightbox\/brightbox-cli"
+ },
+ "commits": [
+ {
+ "id": "a26cf5de9ed9827746d4970872376b10d9325f40",
+ "message": "Release v1.2.2",
+ "timestamp": "2014-04-22T16:46:42+03:00",
+ "url": "http:\/\/localhost:3000\/brightbox\/brightbox-cli\/commit\/a26cf5de9ed9827746d4970872376b10d9325f40",
+ "author": {
+ "name": "Paul Thornthwaite",
+ "email": "tokengeek@gmail.com"
+ }
+ },
+ {
+ "id": "34f57f6ba3ed0c21c5e361bbb041c3591411176c",
+ "message": "Fix server user data update\n\nIncorrect condition was being used so Base64 encoding option was having\nopposite effect from desired.",
+ "timestamp": "2014-04-11T18:17:26+03:00",
+ "url": "http:\/\/localhost:3000\/brightbox\/brightbox-cli\/commit\/34f57f6ba3ed0c21c5e361bbb041c3591411176c",
+ "author": {
+ "name": "Paul Thornthwaite",
+ "email": "tokengeek@gmail.com"
+ }
+ }
+ ],
+ "total_commits_count": 2,
+ "ci_yaml_file":"rspec_linux:\r\n script: ls\r\n"
+ }
+ }
+ </code>
+ </pre>
diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml
new file mode 100644
index 00000000000..a634ae5dfda
--- /dev/null
+++ b/app/views/projects/commit/_ci_menu.html.haml
@@ -0,0 +1,7 @@
+%ul.center-top-menu.commit-ci-menu
+ = nav_link(path: 'commit#show') do
+ = link_to namespace_project_commit_path(@project.namespace, @project, @commit.id) do
+ Changes
+ = nav_link(path: 'commit#ci') do
+ = link_to ci_namespace_project_commit_path(@project.namespace, @project, @commit.id) do
+ Builds
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 3f645b81397..fbf0a9ec0c3 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -38,10 +38,17 @@
- @commit.parents.each do |parent|
= link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent)
+- if @ci_commit
+ .pull-right
+ = link_to ci_status_path(@ci_commit), class: "ci-status ci-#{@ci_commit.status}" do
+ = ci_status_icon(@ci_commit)
+ build:
+ = @ci_commit.status
+
.commit-info-row.branches
%i.fa.fa-spinner.fa-spin
-.commit-box
+.commit-box.gray-content-block.middle-block
%h3.commit-title
= gfm escape_once(@commit.title)
- if @commit.description.present?
diff --git a/app/views/projects/commit/ci.html.haml b/app/views/projects/commit/ci.html.haml
new file mode 100644
index 00000000000..f4382e88046
--- /dev/null
+++ b/app/views/projects/commit/ci.html.haml
@@ -0,0 +1,62 @@
+- page_title "#{@commit.title} (#{@commit.short_id})", "Commits"
+= render "projects/commits/header_title"
+= render "commit_box"
+= render "ci_menu"
+
+- if @ci_project && current_user && can?(current_user, :manage_builds, @project)
+ .pull-right
+ - if @ci_commit.builds.running_or_pending.any?
+ = link_to "Cancel", cancel_ci_project_commits_path(@ci_project, @ci_commit), class: 'btn btn-sm btn-danger'
+
+
+- if @ci_commit.yaml_errors.present?
+ .bs-callout.bs-callout-danger
+ %h4 Found errors in your .gitlab-ci.yml:
+ %ul
+ - @ci_commit.yaml_errors.split(",").each do |error|
+ %li= error
+
+- unless @ci_commit.ci_yaml_file
+ .bs-callout.bs-callout-warning
+ \.gitlab-ci.yml not found in this commit
+
+- @ci_commit.refs.each do |ref|
+ .gray-content-block.second-block
+ Builds for #{ref}
+ - if @ci_commit.duration_for_ref(ref) > 0
+ %small.pull-right
+ %i.fa.fa-time
+ #{time_interval_in_words @ci_commit.duration_for_ref(ref)}
+
+ %table.table.builds
+ %thead
+ %tr
+ %th Status
+ %th Build ID
+ %th Stage
+ %th Name
+ %th Duration
+ %th Finished at
+ - if @ci_project && @ci_project.coverage_enabled?
+ %th Coverage
+ %th
+ = render partial: "projects/builds/build", collection: @ci_commit.builds_without_retry.for_ref(ref), controls: true
+
+- if @ci_commit.retried_builds.any?
+ %h3
+ Retried builds
+
+ %table.table.builds
+ %thead
+ %tr
+ %th Status
+ %th Build ID
+ %th Ref
+ %th Stage
+ %th Name
+ %th Duration
+ %th Finished at
+ - if @ci_project && @ci_project.coverage_enabled?
+ %th Coverage
+ %th
+ = render partial: "projects/builds/build", collection: @ci_commit.retried_builds, ref: true
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index 60b112e67d4..30a3973828f 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -1,4 +1,6 @@
- page_title "#{@commit.title} (#{@commit.short_id})", "Commits"
+= render "projects/commits/header_title"
= render "commit_box"
+= render "ci_menu" if @ci_commit
= render "projects/diffs/diffs", diffs: @diffs, project: @project
= render "projects/notes/notes_with_form", view: params[:view]
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 74f8d8b15cf..cddd5aa3a83 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -4,7 +4,11 @@
- notes = commit.notes
- note_count = notes.user.count
-= cache [project.id, commit.id, note_count] do
+- ci_commit = project.ci_commit(commit.sha)
+- cache_key = [project.path_with_namespace, commit.id, note_count]
+- cache_key.push(ci_commit.status) if ci_commit
+
+= cache(cache_key) do
%li.commit.js-toggle-container
.commit-row-title
%strong.str-truncated
@@ -13,6 +17,10 @@
%a.text-expander.js-toggle-button ...
.pull-right
+ - if ci_commit
+ = link_to ci_status_path(ci_commit), class: "c#{ci_status_color(ci_commit)}" do
+ = ci_status_icon(ci_commit)
+ &nbsp;
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
.notes_count
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index e3d8cd0fdd5..a849bf84698 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -1,22 +1,18 @@
-%ul.nav.nav-tabs
+%ul.center-top-menu
= nav_link(controller: [:commit, :commits]) do
- = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref) do
- = icon("history")
+ = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
Commits
%span.badge= number_with_delimiter(@repository.commit_count)
= nav_link(controller: :compare) do
- = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: @ref || @repository.root_ref) do
- = icon("exchange")
+ = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do
Compare
= nav_link(html_options: {class: branches_tab_class}) do
= link_to namespace_project_branches_path(@project.namespace, @project) do
- = icon("code-fork")
Branches
%span.badge.js-totalbranch-count= @repository.branches.size
= nav_link(controller: :tags) do
= link_to namespace_project_tags_path(@project.namespace, @project) do
- = icon("tags")
Tags
%span.badge.js-totaltags-count= @repository.tags.length
diff --git a/app/views/projects/commits/_header_title.html.haml b/app/views/projects/commits/_header_title.html.haml
new file mode 100644
index 00000000000..e4385893dd9
--- /dev/null
+++ b/app/views/projects/commits/_header_title.html.haml
@@ -0,0 +1 @@
+- header_title project_title(@project, "Commits", project_commits_path(@project))
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index 55054a31977..2dd99cc8215 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -1,26 +1,28 @@
- page_title "Commits", @ref
+= render "header_title"
= content_for :meta_tags do
- if current_user
= auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits")
= render "head"
-.tree-ref-holder
- = render 'shared/ref_switcher', destination: 'commits'
+.gray-content-block
+ .tree-ref-holder
+ = render 'shared/ref_switcher', destination: 'commits'
-.commits-feed-holder.hidden-xs.hidden-sm
- - if create_mr_button?(@repository.root_ref, @ref)
- = link_to create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' do
- = icon('plus')
- Create Merge Request
+ .commits-feed-holder.hidden-xs.hidden-sm
+ - if create_mr_button?(@repository.root_ref, @ref)
+ = link_to create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' do
+ = icon('plus')
+ Create Merge Request
- - if current_user && current_user.private_token
- = link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Commits Feed", class: 'prepend-left-10 btn' do
- = icon("rss")
+ - if current_user && current_user.private_token
+ = link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Commits Feed", class: 'prepend-left-10 btn' do
+ = icon("rss")
-%ul.breadcrumb.repo-breadcrumb
- = commits_breadcrumbs
+ %ul.breadcrumb.repo-breadcrumb
+ = commits_breadcrumbs
%div{id: dom_id(@project)}
#commits-list= render "commits", project: @project
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index 3019893d12c..efc25eda26b 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -1,5 +1,5 @@
= form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline js-requires-input' do
- .clearfix.append-bottom-20
+ .clearfix
- if params[:to] && params[:from]
= link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has_tooltip', title: 'Switch base of comparison'}
.form-group
diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml
index d1e579a2ede..02be5a2d07f 100644
--- a/app/views/projects/compare/index.html.haml
+++ b/app/views/projects/compare/index.html.haml
@@ -1,9 +1,8 @@
- page_title "Compare"
+= render "projects/commits/header_title"
= render "projects/commits/head"
-%h3.page-title
- Compare View
-%p.slead
+.gray-content-block
Compare branches, tags or commit ranges.
%br
Fill input field with commit id like
@@ -14,4 +13,5 @@
%br
Changes are shown <b>from</b> the version in the first field <b>to</b> the version in the second field.
-= render "form"
+.prepend-top-20
+ = render "form"
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index 3670dd5c13b..39755efd2fd 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -1,16 +1,17 @@
- page_title "#{params[:from]}...#{params[:to]}"
+= render "projects/commits/header_title"
= render "projects/commits/head"
-%h3.page-title
- Compare View
-= render "form"
+.gray-content-block
+ = render "form"
- if @commits.present?
- = render "projects/commits/commit_list"
- = render "projects/diffs/diffs", diffs: @diffs, project: @project
+ .prepend-top-20
+ = render "projects/commits/commit_list"
+ = render "projects/diffs/diffs", diffs: @diffs, project: @project
- else
- .light-well
+ .light-well.prepend-top-20
.center
%h4
There isn't anything to compare.
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 30943f49bba..4f1965bfb39 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -1,21 +1,26 @@
- if params[:view] == 'parallel'
- fluid_layout true
-.prepend-top-20.append-bottom-20
- .pull-right
+- diff_files = safe_diff_files(diffs)
+
+.gray-content-block.second-block
+ .inline-parallel-buttons
.btn-group
= inline_diff_btn
= parallel_diff_btn
- = render 'projects/diffs/stats', diffs: diffs
-
-- diff_files = safe_diff_files(diffs)
+ = render 'projects/diffs/stats', diff_files: diff_files
- if diff_files.count < diffs.size
= render 'projects/diffs/warning', diffs: diffs, shown_files_count: diff_files.count
.files
- diff_files.each_with_index do |diff_file, index|
- = render 'projects/diffs/file', diff_file: diff_file, i: index, project: project
+ - diff_commit = commit_for_diff(diff_file.diff)
+ - blob = project.repository.blob_for_diff(diff_commit, diff_file.diff)
+ - next unless blob
+
+ = render 'projects/diffs/file', i: index, project: project,
+ diff_file: diff_file, diff_commit: diff_commit, blob: blob
- if @diff_timeout
.alert.alert-danger
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 99ee23a1ddc..4617b188150 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -1,24 +1,18 @@
-- blob = project.repository.blob_for_diff(@commit, diff_file.diff)
-- return unless blob
-- blob_diff_path = namespace_project_blob_diff_path(project.namespace, project, tree_join(@commit.id, diff_file.file_path))
-.diff-file{id: "diff-#{i}", data: {blob_diff_path: blob_diff_path }}
+.diff-file{id: "diff-#{i}", data: diff_file_html_data(project, diff_commit, diff_file)}
.diff-header{id: "file-path-#{hexdigest(diff_file.new_path || diff_file.old_path)}"}
- - if diff_file.deleted_file
- %span="#{diff_file.old_path} deleted"
-
- .diff-btn-group
- - if @commit.parent_ids.present?
- = view_file_btn(@commit.parent_id, diff_file, project)
- - elsif diff_file.diff.submodule?
+ - if diff_file.diff.submodule?
%span
- submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path)
= submodule_link(submodule_item, @commit.id, project.repository)
- else
%span
- - if diff_file.renamed_file
+ - if diff_file.deleted_file
+ = "#{diff_file.old_path} deleted"
+ - elsif diff_file.renamed_file
= "#{diff_file.old_path} renamed to #{diff_file.new_path}"
- else
= diff_file.new_path
+
- if diff_file.mode_changed?
%span.file-mode= "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}"
@@ -28,12 +22,12 @@
%i.fa.fa-comments
&nbsp;
- - if @merge_request && @merge_request.source_project
+ - if editable_diff?(diff_file)
= edit_blob_link(@merge_request.source_project,
@merge_request.source_branch, diff_file.new_path,
after: '&nbsp;', from_merge_request_id: @merge_request.id)
- = view_file_btn(@commit.id, diff_file, project)
+ = view_file_btn(diff_commit.id, diff_file, project)
.diff-content.diff-wrap-lines
-# Skipp all non non-supported blobs
diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
index 1625930615a..ea2a3e01277 100644
--- a/app/views/projects/diffs/_stats.html.haml
+++ b/app/views/projects/diffs/_stats.html.haml
@@ -2,37 +2,35 @@
.commit-stat-summary
Showing
= link_to '#', class: 'js-toggle-button' do
- %strong #{pluralize(diffs.count, "changed file")}
- - if current_controller?(:commit)
- - unless @commit.has_zero_stats?
- with
- %strong.cgreen #{@commit.stats.additions} additions
- and
- %strong.cred #{@commit.stats.deletions} deletions
+ %strong #{pluralize(diff_files.count, "changed file")}
+ with
+ %strong.cgreen #{diff_files.sum(&:added_lines)} additions
+ and
+ %strong.cred #{diff_files.sum(&:removed_lines)} deletions
.file-stats.js-toggle-content.hide
- %ul.bordered-list
- - diffs.each_with_index do |diff, i|
+ %ul
+ - diff_files.each_with_index do |diff_file, i|
%li
- - if diff.deleted_file
+ - if diff_file.deleted_file
%span.deleted-file
%a{href: "#diff-#{i}"}
%i.fa.fa-minus
- = diff.old_path
- - elsif diff.renamed_file
+ = diff_file.old_path
+ - elsif diff_file.renamed_file
%span.renamed-file
%a{href: "#diff-#{i}"}
%i.fa.fa-minus
- = diff.old_path
+ = diff_file.old_path
&rarr;
- = diff.new_path
- - elsif diff.new_file
+ = diff_file.new_path
+ - elsif diff_file.new_file
%span.new-file
%a{href: "#diff-#{i}"}
%i.fa.fa-plus
- = diff.new_path
+ = diff_file.new_path
- else
%span.edit-file
%a{href: "#diff-#{i}"}
%i.fa.fa-adjust
- = diff.new_path
+ = diff_file.new_path
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index e8e65d87f47..90dce739992 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -1,10 +1,11 @@
+- @blank_container = true
+
.project-edit-container
.project-edit-errors
.project-edit-content
- %div
- %h3.page-title
+ .panel.panel-default
+ .panel-heading
Project settings
- %hr
.panel-body
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit_project form-horizontal fieldset-form" }, authenticity_token: true do |f|
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index e577d35d560..e06454fd148 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -1,10 +1,11 @@
-- if current_user && can?(current_user, :download_code, @project)
- = render 'shared/no_ssh'
- = render 'shared/no_password'
-
+.alert_holder
+ - if current_user && can?(current_user, :download_code, @project)
+ = render 'shared/no_ssh'
+ = render 'shared/no_password'
+
= render "home_panel"
-.center.light-well
+.gray-content-block.center
%h3.page-title
The repository for this project is empty
%p
@@ -15,38 +16,39 @@
file to this project.
.prepend-top-20
-%h3.page-title
- Command line instructions
-%div.git-empty
- %fieldset
- %h5 Git global setup
- %pre.light-well
- :preserve
- git config --global user.name "#{git_user_name}"
- git config --global user.email "#{git_user_email}"
+.empty_wrapper
+ %h3.page-title-empty
+ Command line instructions
+ %div.git-empty
+ %fieldset
+ %h5 Git global setup
+ %pre.light-well
+ :preserve
+ git config --global user.name "#{h git_user_name}"
+ git config --global user.email "#{h git_user_email}"
- %fieldset
- %h5 Create a new repository
- %pre.light-well
- :preserve
- git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')}
- cd #{@project.path}
- touch README.md
- git add README.md
- git commit -m "add README"
- git push -u origin master
+ %fieldset
+ %h5 Create a new repository
+ %pre.light-well
+ :preserve
+ git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')}
+ cd #{h @project.path}
+ touch README.md
+ git add README.md
+ git commit -m "add README"
+ git push -u origin master
- %fieldset
- %h5 Existing folder or Git repository
- %pre.light-well
- :preserve
- cd existing_folder
- git init
- git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
- git add .
- git commit
- git push -u origin master
+ %fieldset
+ %h5 Existing folder or Git repository
+ %pre.light-well
+ :preserve
+ cd existing_folder
+ git init
+ git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
+ git add .
+ git commit
+ git push -u origin master
-- if can? current_user, :remove_project, @project
- .prepend-top-20
- = link_to 'Remove project', [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
+ - if can? current_user, :remove_project, @project
+ .prepend-top-20
+ = link_to 'Remove project', [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml
index b7a2ed68e25..cd5f3a5d39e 100644
--- a/app/views/projects/forks/new.html.haml
+++ b/app/views/projects/forks/new.html.haml
@@ -10,21 +10,22 @@
- group.each do |namespace|
.col-md-2.col-sm-3
- if fork = namespace.find_fork_of(@project)
- .thumbnail.fork-exists-thumbnail
+ .fork-thumbnail
= link_to project_path(fork), title: "Visit project fork", class: 'has_tooltip' do
- = image_tag namespace_icon(namespace, 200)
+ = image_tag namespace_icon(namespace, 100)
.caption
- %h4=namespace.human_name
- %p
- = namespace.path
+ %strong
+ = namespace.human_name
+ %div.text-primary
+ Already forked
+
- else
- .thumbnail.fork-thumbnail
+ .fork-thumbnail
= link_to namespace_project_fork_path(@project.namespace, @project, namespace_key: namespace.id), title: "Fork here", method: "POST", class: 'has_tooltip' do
- = image_tag namespace_icon(namespace, 200)
+ = image_tag namespace_icon(namespace, 100)
.caption
- %h4=namespace.human_name
- %p
- = namespace.path
+ %strong
+ = namespace.human_name
%p.light
Fork is a copy of a project repository.
diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml
index 9383df13305..bbfaf422a82 100644
--- a/app/views/projects/graphs/_head.html.haml
+++ b/app/views/projects/graphs/_head.html.haml
@@ -3,3 +3,7 @@
= link_to 'Contributors', namespace_project_graph_path
= nav_link(action: :commits) do
= link_to 'Commits', commits_namespace_project_graph_path
+ - if @project.gitlab_ci?
+ = nav_link(action: :ci) do
+ = link_to ci_namespace_project_graph_path do
+ Continuous Integration
diff --git a/app/views/projects/graphs/_header_title.html.haml b/app/views/projects/graphs/_header_title.html.haml
new file mode 100644
index 00000000000..1e2f61cd22b
--- /dev/null
+++ b/app/views/projects/graphs/_header_title.html.haml
@@ -0,0 +1 @@
+- header_title project_title(@project, "Graphs", namespace_project_graph_path(@project.namespace, @project, current_ref))
diff --git a/app/views/projects/graphs/ci.html.haml b/app/views/projects/graphs/ci.html.haml
new file mode 100644
index 00000000000..4f69cc64f7c
--- /dev/null
+++ b/app/views/projects/graphs/ci.html.haml
@@ -0,0 +1,7 @@
+- page_title "Continuous Integration", "Graphs"
+= render "header_title"
+= render 'head'
+#charts.ci-charts
+ = render 'projects/graphs/ci/builds'
+ = render 'projects/graphs/ci/build_times'
+= render 'projects/graphs/ci/overall'
diff --git a/app/views/projects/graphs/ci/_build_times.haml b/app/views/projects/graphs/ci/_build_times.haml
new file mode 100644
index 00000000000..c3c2f572414
--- /dev/null
+++ b/app/views/projects/graphs/ci/_build_times.haml
@@ -0,0 +1,21 @@
+%fieldset
+ %legend
+ Commit duration in minutes for last 30 commits
+
+ %canvas#build_timesChart.padded{width: 800, height: 300}
+
+:javascript
+ var data = {
+ labels : #{@charts[:build_times].labels.to_json},
+ datasets : [
+ {
+ fillColor : "#4A3",
+ strokeColor : "rgba(151,187,205,1)",
+ pointColor : "rgba(151,187,205,1)",
+ pointStrokeColor : "#fff",
+ data : #{@charts[:build_times].build_times.to_json}
+ }
+ ]
+ }
+ var ctx = $("#build_timesChart").get(0).getContext("2d");
+ new Chart(ctx).Line(data,{"scaleOverlay": true});
diff --git a/app/views/projects/graphs/ci/_builds.haml b/app/views/projects/graphs/ci/_builds.haml
new file mode 100644
index 00000000000..1b0039fb834
--- /dev/null
+++ b/app/views/projects/graphs/ci/_builds.haml
@@ -0,0 +1,41 @@
+%fieldset
+ %legend
+ Builds chart for last week
+ (#{date_from_to(Date.today - 7.days, Date.today)})
+
+ %canvas#weekChart.padded{width: 800, height: 200}
+
+%fieldset
+ %legend
+ Builds chart for last month
+ (#{date_from_to(Date.today - 30.days, Date.today)})
+
+ %canvas#monthChart.padded{width: 800, height: 300}
+
+%fieldset
+ %legend Builds chart for last year
+ %canvas#yearChart.padded{width: 800, height: 400}
+
+- [:week, :month, :year].each do |scope|
+ :javascript
+ var data = {
+ labels : #{@charts[scope].labels.to_json},
+ datasets : [
+ {
+ fillColor : "rgba(220,220,220,0.5)",
+ strokeColor : "rgba(220,220,220,1)",
+ pointColor : "rgba(220,220,220,1)",
+ pointStrokeColor : "#EEE",
+ data : #{@charts[scope].total.to_json}
+ },
+ {
+ fillColor : "#4A3",
+ strokeColor : "rgba(151,187,205,1)",
+ pointColor : "rgba(151,187,205,1)",
+ pointStrokeColor : "#fff",
+ data : #{@charts[scope].success.to_json}
+ }
+ ]
+ }
+ var ctx = $("##{scope}Chart").get(0).getContext("2d");
+ new Chart(ctx).Line(data,{"scaleOverlay": true});
diff --git a/app/views/projects/graphs/ci/_overall.haml b/app/views/projects/graphs/ci/_overall.haml
new file mode 100644
index 00000000000..9550d719471
--- /dev/null
+++ b/app/views/projects/graphs/ci/_overall.haml
@@ -0,0 +1,22 @@
+- ci_project = @project.gitlab_ci_project
+%fieldset
+ %legend Overall
+ %p
+ Total:
+ %strong= pluralize ci_project.builds.count(:all), 'build'
+ %p
+ Successful:
+ %strong= pluralize ci_project.builds.success.count(:all), 'build'
+ %p
+ Failed:
+ %strong= pluralize ci_project.builds.failed.count(:all), 'build'
+
+ %p
+ Success ratio:
+ %strong
+ #{success_ratio(ci_project.builds.success, ci_project.builds.failed)}%
+
+ %p
+ Commits covered:
+ %strong
+ = ci_project.commits.count(:all)
diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml
index a357736bf52..112be875b6b 100644
--- a/app/views/projects/graphs/commits.html.haml
+++ b/app/views/projects/graphs/commits.html.haml
@@ -1,4 +1,5 @@
-- page_title "Commit statistics"
+- page_title "Commits", "Graphs"
+= render "header_title"
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'graphs_commits'
= render 'head'
@@ -31,61 +32,55 @@
%div
%p.slead
Commits per day of month
- %canvas#month-chart{width: 800, height: 400}
+ %canvas#month-chart
.row
.col-md-6
%div
%p.slead
Commits per day hour (UTC)
- %canvas#hour-chart{width: 800, height: 400}
+ %canvas#hour-chart
.col-md-6
%div
%p.slead
Commits per weekday
- %canvas#weekday-chart{width: 800, height: 400}
+ %canvas#weekday-chart
:coffeescript
- data = {
- labels : #{@commits_per_time.keys.to_json},
- datasets : [{
- fillColor : "rgba(220,220,220,0.5)",
- strokeColor : "rgba(220,220,220,1)",
- barStrokeWidth: 1,
- barValueSpacing: 1,
- barDatasetSpacing: 1,
- data : #{@commits_per_time.values.to_json}
- }]
- }
+ responsiveChart = (selector, data) ->
+ options = { "scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2, maintainAspectRatio: false }
- ctx = $("#hour-chart").get(0).getContext("2d");
- new Chart(ctx).Bar(data,{"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2})
+ # get selector by context
+ ctx = selector.get(0).getContext("2d")
+ # pointing parent container to make chart.js inherit its width
+ container = $(selector).parent()
- data = {
- labels : #{@commits_per_week_days.keys.to_json},
- datasets : [{
- fillColor : "rgba(220,220,220,0.5)",
- strokeColor : "rgba(220,220,220,1)",
- barStrokeWidth: 1,
- barValueSpacing: 1,
- barDatasetSpacing: 1,
- data : #{@commits_per_week_days.values.to_json}
- }]
- }
+ generateChart = ->
+ selector.attr('width', $(container).width())
+ new Chart(ctx).Bar(data, options)
+
+ # enabling auto-resizing
+ $(window).resize( generateChart )
- ctx = $("#weekday-chart").get(0).getContext("2d");
- new Chart(ctx).Bar(data,{"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2})
+ generateChart()
- data = {
- labels : #{@commits_per_month.keys.to_json},
- datasets : [{
- fillColor : "rgba(220,220,220,0.5)",
- strokeColor : "rgba(220,220,220,1)",
- barStrokeWidth: 1,
- barValueSpacing: 1,
- barDatasetSpacing: 1,
- data : #{@commits_per_month.values.to_json}
- }]
+ chartData = (keys, values) ->
+ data = {
+ labels : keys,
+ datasets : [{
+ fillColor : "rgba(220,220,220,0.5)",
+ strokeColor : "rgba(220,220,220,1)",
+ barStrokeWidth: 1,
+ barValueSpacing: 1,
+ barDatasetSpacing: 1,
+ data : values
+ }]
}
- ctx = $("#month-chart").get(0).getContext("2d");
- new Chart(ctx).Bar(data, {"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2})
+ hourData = chartData(#{@commits_per_time.keys.to_json}, #{@commits_per_time.values.to_json})
+ responsiveChart($('#hour-chart'), hourData)
+
+ dayData = chartData(#{@commits_per_week_days.keys.to_json}, #{@commits_per_week_days.values.to_json})
+ responsiveChart($('#weekday-chart'), dayData)
+
+ monthData = chartData(#{@commits_per_month.keys.to_json}, #{@commits_per_month.values.to_json})
+ responsiveChart($('#month-chart'), monthData)
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index ecdd0eaf52f..bd342911e49 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -1,4 +1,5 @@
-- page_title "Contributor statistics"
+- page_title "Contributors", "Graphs"
+= render "header_title"
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'graphs'
= render 'head'
diff --git a/app/views/projects/imports/show.html.haml b/app/views/projects/imports/show.html.haml
index 39fe0fc1c4f..06886d215a3 100644
--- a/app/views/projects/imports/show.html.haml
+++ b/app/views/projects/imports/show.html.haml
@@ -3,8 +3,12 @@
.center
%h2
%i.fa.fa-spinner.fa-spin
- Import in progress.
- %p.monospace git clone --bare #{hidden_pass_url(@project.import_url)}
+ - if @project.forked?
+ Forking in progress.
+ - else
+ Import in progress.
+ - unless @project.forked?
+ %p.monospace git clone --bare #{hidden_pass_url(@project.import_url)}
%p Please wait while we import the repository for you. Refresh at will.
:javascript
new ProjectImport();
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index f61ae957208..d4a98eca473 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -7,21 +7,24 @@
= render 'shared/show_aside'
+.gray-content-block.second-block
+ .row
+ .col-md-9
+ .votes-holder.pull-right
+ #votes= render 'votes/votes_block', votable: @issue
+ .participants
+ %span= pluralize(@participants.count, 'participant')
+ - @participants.each do |participant|
+ = link_to_member(@project, participant, name: false, size: 24)
+ .col-md-3
+ %span.slead.has_tooltip{title: 'Cross-project reference'}
+ = cross_project_reference(@project, @issue)
+
.row
%section.col-md-9
- .votes-holder.pull-right
- #votes= render 'votes/votes_block', votable: @issue
- .participants
- %span= pluralize(@participants.count, 'participant')
- - @participants.each do |participant|
- = link_to_member(@project, participant, name: false, size: 24)
.voting_notes#notes= render 'projects/notes/notes_with_form'
%aside.col-md-3
.issuable-affix
- .clearfix
- %span.slead.has_tooltip{title: 'Cross-project reference'}
- = cross_project_reference(@project, @issue)
- %hr
.context
= render 'shared/issuable/context', issuable: @issue
diff --git a/app/views/projects/issues/_header_title.html.haml b/app/views/projects/issues/_header_title.html.haml
new file mode 100644
index 00000000000..99f03549c44
--- /dev/null
+++ b/app/views/projects/issues/_header_title.html.haml
@@ -0,0 +1 @@
+- header_title project_title(@project, "Issues", namespace_project_issues_path(@project.namespace, @project))
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index b6910c8f796..55ce912829d 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -41,4 +41,4 @@
= issue.task_status
.pull-right.issue-updated-at
- %small updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')}
+ %span updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')}
diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml
index 5d243adb5fe..a3399c57aa2 100644
--- a/app/views/projects/issues/_issues.html.haml
+++ b/app/views/projects/issues/_issues.html.haml
@@ -1,9 +1,8 @@
-.panel.panel-default
- %ul.well-list.issues-list
- = render @issues
- - if @issues.blank?
- %li
- .nothing-here-block No issues to show
+%ul.content-list.issues-list
+ = render @issues
+ - if @issues.blank?
+ %li
+ .nothing-here-block No issues to show
- if @issues.present?
.pull-right
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index d06225f5488..d6260ab2900 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -1,10 +1,12 @@
- page_title "Issues"
+= render "header_title"
+
= content_for :meta_tags do
- if current_user
= auto_discovery_link_tag(:atom, namespace_project_issues_url(@project.namespace, @project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues")
-.append-bottom-10
- .pull-right
+.project-issuable-filter
+ .controls
.pull-left
- if current_user
.hidden-xs.pull-left
diff --git a/app/views/projects/issues/new.html.haml b/app/views/projects/issues/new.html.haml
index da6edd5c2d2..153447baa1b 100644
--- a/app/views/projects/issues/new.html.haml
+++ b/app/views/projects/issues/new.html.haml
@@ -1,2 +1,4 @@
- page_title "New Issue"
+= render "header_title"
+
= render "form"
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index e7b14e7582c..5cb814c9ea8 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -1,15 +1,18 @@
- page_title "#{@issue.title} (##{@issue.iid})", "Issues"
+= render "header_title"
+
.issue
.issue-details.issuable-details
- %h4.page-title
+ .page-title
.issue-box{ class: issue_box_class(@issue) }
- if @issue.closed?
Closed
- else
Open
- Issue ##{@issue.iid}
- %small.creator
- &middot; created by #{link_to_member(@project, @issue.author)}
+ %span.issue-id Issue ##{@issue.iid}
+ %span.creator
+ &middot; created by #{link_to_member(@project, @issue.author, size: 24)}
+ &middot;
= time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago')
- if @issue.updated_at != @issue.created_at
%span
@@ -32,18 +35,17 @@
= icon('pencil-square-o')
Edit
- %hr
- %h2.issue-title
- = gfm escape_once(@issue.title)
- %div
- - if @issue.description.present?
- .description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''}
- .wiki
- = preserve do
- = markdown(@issue.description)
- %textarea.hidden.js-task-list-field
- = @issue.description
+ .gray-content-block.middle-block
+ %h2.issue-title
+ = gfm escape_once(@issue.title)
+ %div
+ - if @issue.description.present?
+ .description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''}
+ .wiki
+ = preserve do
+ = markdown(@issue.description)
+ %textarea.hidden.js-task-list-field
+ = @issue.description
- %hr
.issue-discussion
= render 'projects/issues/discussion'
diff --git a/app/views/projects/labels/_header_title.html.haml b/app/views/projects/labels/_header_title.html.haml
new file mode 100644
index 00000000000..abe28da483b
--- /dev/null
+++ b/app/views/projects/labels/_header_title.html.haml
@@ -0,0 +1 @@
+- header_title project_title(@project, "Labels", namespace_project_labels_path(@project.namespace, @project))
diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml
index 645402667fd..bc4ab0ca27c 100644
--- a/app/views/projects/labels/edit.html.haml
+++ b/app/views/projects/labels/edit.html.haml
@@ -1,4 +1,6 @@
- page_title "Edit", @label.name, "Labels"
+= render "header_title"
+
%h3
Edit label
%span.light #{@label.name}
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index d44fe486212..97175f8232b 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -1,14 +1,16 @@
- page_title "Labels"
-- if can? current_user, :admin_label, @project
- = link_to new_namespace_project_label_path(@project.namespace, @project), class: "pull-right btn btn-new" do
- New label
-%h3.page-title
- Labels
-%hr
+= render "header_title"
+
+.gray-content-block.top-block
+ - if can? current_user, :admin_label, @project
+ = link_to new_namespace_project_label_path(@project.namespace, @project), class: "pull-right btn btn-new" do
+ New label
+ .oneline
+ Labels can be applied to issues and merge requests.
.labels
- if @labels.present?
- %ul.bordered-list.manage-labels-list
+ %ul.content-list.manage-labels-list
= render @labels
= paginate @labels, theme: 'gitlab'
- else
diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml
index b3ef17025c3..342ad4f3f95 100644
--- a/app/views/projects/labels/new.html.haml
+++ b/app/views/projects/labels/new.html.haml
@@ -1,4 +1,6 @@
- page_title "New Label"
+= render "header_title"
+
%h3 New label
.back-link
= link_to namespace_project_labels_path(@project.namespace, @project) do
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index f855dfec321..38e66c3828b 100644
--- a/app/views/projects/merge_requests/_discussion.html.haml
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -7,18 +7,21 @@
= render 'shared/show_aside'
+.gray-content-block.second-block
+ .row
+ .col-md-9
+ .votes-holder.pull-right
+ #votes= render 'votes/votes_block', votable: @merge_request
+ = render "projects/merge_requests/show/participants"
+ .col-md-3
+ %span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'}
+ = cross_project_reference(@project, @merge_request)
+
.row
%section.col-md-9
- .votes-holder.pull-right
- #votes= render 'votes/votes_block', votable: @merge_request
- = render "projects/merge_requests/show/participants"
= render "projects/notes/notes_with_form"
%aside.col-md-3
.issuable-affix
- .clearfix
- %span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'}
- = cross_project_reference(@project, @merge_request)
- %hr
.context
= render 'shared/issuable/context', issuable: @merge_request
diff --git a/app/views/projects/merge_requests/_header_title.html.haml b/app/views/projects/merge_requests/_header_title.html.haml
new file mode 100644
index 00000000000..669a9b06bdf
--- /dev/null
+++ b/app/views/projects/merge_requests/_header_title.html.haml
@@ -0,0 +1 @@
+- header_title project_title(@project, "Merge Requests", namespace_project_merge_requests_path(@project.namespace, @project))
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 0bcd543fee7..25e4e8ba80d 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -14,11 +14,6 @@
%span
%i.fa.fa-ban
CLOSED
- - else
- %span.hidden-xs.hidden-sm
- %span.label-branch<
- %i.fa.fa-code-fork
- %span= merge_request.target_branch
- note_count = merge_request.mr_and_commit_notes.user.count
- if merge_request.assignee
&nbsp;
@@ -48,4 +43,4 @@
= merge_request.task_status
.pull-right.hidden-xs
- %small updated #{time_ago_with_tooltip(merge_request.updated_at, placement: 'bottom', html_class: 'merge_request_updated_ago')}
+ %span updated #{time_ago_with_tooltip(merge_request.updated_at, placement: 'bottom', html_class: 'merge_request_updated_ago')}
diff --git a/app/views/projects/merge_requests/_merge_requests.html.haml b/app/views/projects/merge_requests/_merge_requests.html.haml
index b8a0ca9a42f..d86707b3d97 100644
--- a/app/views/projects/merge_requests/_merge_requests.html.haml
+++ b/app/views/projects/merge_requests/_merge_requests.html.haml
@@ -1,9 +1,8 @@
-.panel.panel-default
- %ul.well-list.mr-list
- = render @merge_requests
- - if @merge_requests.blank?
- %li
- .nothing-here-block No merge requests to show
+%ul.content-list.mr-list
+ = render @merge_requests
+ - if @merge_requests.blank?
+ %li
+ .nothing-here-block No merge requests to show
- if @merge_requests.present?
.pull-right
diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml
index 7709330611a..452006162db 100644
--- a/app/views/projects/merge_requests/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/_new_compare.html.haml
@@ -37,7 +37,7 @@
%h4 Compare failed
%p We can't compare selected branches. It may be because of huge diff. Please try again or select different branches.
- else
- .light-well
+ .light-well.append-bottom-10
.center
%h4
There isn't anything to merge.
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 76f44211dac..6244d3ba0b4 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -1,10 +1,11 @@
%h3.page-title
New merge request
%p.slead
+ - source_title, target_title = format_mr_branch_names(@merge_request)
From
- %strong.label-branch #{@merge_request.source_project_namespace}:#{@merge_request.source_branch}
+ %strong.label-branch #{source_title}
%span into
- %strong.label-branch #{@merge_request.target_project_namespace}:#{@merge_request.target_branch}
+ %strong.label-branch #{target_title}
%span.pull-right
= link_to 'Change branches', mr_change_branches_path(@merge_request)
@@ -18,15 +19,13 @@
= f.hidden_field :target_branch
.mr-compare.merge-request
- %ul.nav.nav-tabs.merge-request-tabs
+ %ul.merge-request-tabs
%li.commits-tab
= link_to url_for(params), data: {target: '#commits', action: 'commits', toggle: 'tab'} do
- = icon('history')
Commits
%span.badge= @commits.size
%li.diffs-tab.active
= link_to url_for(params), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
- = icon('list-alt')
Changes
%span.badge= @diffs.size
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index ec1838eb489..0b0f52c653c 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -1,14 +1,14 @@
- page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests"
+= render "header_title"
+
- if params[:view] == 'parallel'
- fluid_layout true
.merge-request{'data-url' => merge_request_path(@merge_request)}
.merge-request-details.issuable-details
= render "projects/merge_requests/show/mr_title"
- %hr
= render "projects/merge_requests/show/mr_box"
- %hr
- .append-bottom-20.mr-source-target
+ .append-bottom-20.mr-source-target.prepend-top-default
- if @merge_request.open?
.pull-right
- if @merge_request.source_branch_exists?
@@ -39,20 +39,17 @@
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
- if @commits.present?
- %ul.nav.nav-tabs.merge-request-tabs
+ %ul.merge-request-tabs
%li.notes-tab
= link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#notes', action: 'notes', toggle: 'tab'} do
- = icon('comments')
Discussion
%span.badge= @merge_request.mr_and_commit_notes.user.count
%li.commits-tab
= link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#commits', action: 'commits', toggle: 'tab'} do
- = icon('history')
Commits
%span.badge= @commits.size
%li.diffs-tab
= link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
- = icon('list-alt')
Changes
%span.badge= @merge_request.diffs.size
diff --git a/app/views/projects/merge_requests/edit.html.haml b/app/views/projects/merge_requests/edit.html.haml
index 7e5cb07f249..303ca0a880b 100644
--- a/app/views/projects/merge_requests/edit.html.haml
+++ b/app/views/projects/merge_requests/edit.html.haml
@@ -1,4 +1,6 @@
- page_title "Edit", "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests"
+= render "header_title"
+
%h3.page-title
= "Edit merge request ##{@merge_request.iid}"
%hr
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index 72fbe2e27a7..086298e5af1 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -1,7 +1,9 @@
- page_title "Merge Requests"
+= render "header_title"
+
= render 'projects/last_push'
-.append-bottom-10
- .pull-right
+.project-issuable-filter
+ .controls
= render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project)
- if can? current_user, :create_merge_request, @project
diff --git a/app/views/projects/merge_requests/invalid.html.haml b/app/views/projects/merge_requests/invalid.html.haml
index 15bd4e2fafd..fc03ee73a3d 100644
--- a/app/views/projects/merge_requests/invalid.html.haml
+++ b/app/views/projects/merge_requests/invalid.html.haml
@@ -1,4 +1,6 @@
- page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests"
+= render "header_title"
+
.merge-request
= render "projects/merge_requests/show/mr_title"
= render "projects/merge_requests/show/mr_box"
diff --git a/app/views/projects/merge_requests/new.html.haml b/app/views/projects/merge_requests/new.html.haml
index b038a640f67..9fdde80c6d9 100644
--- a/app/views/projects/merge_requests/new.html.haml
+++ b/app/views/projects/merge_requests/new.html.haml
@@ -1,4 +1,6 @@
- page_title "New Merge Request"
+= render "header_title"
+
- if @merge_request.can_be_created
= render 'new_submit'
- else
diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
index db1575f899a..f18cf96c17d 100644
--- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml
+++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
@@ -11,12 +11,12 @@
%pre.dark
- if @merge_request.for_fork?
:preserve
- git fetch #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch}
- git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} FETCH_HEAD
+ git fetch #{h @merge_request.source_project.http_url_to_repo} #{h @merge_request.source_branch}
+ git checkout -b #{h @merge_request.source_project_path}-#{h @merge_request.source_branch} FETCH_HEAD
- else
:preserve
git fetch origin
- git checkout -b #{@merge_request.source_branch} origin/#{@merge_request.source_branch}
+ git checkout -b #{h @merge_request.source_branch} origin/#{h @merge_request.source_branch}
%p
%strong Step 2.
Review the changes locally
@@ -27,18 +27,18 @@
%pre.dark
- if @merge_request.for_fork?
:preserve
- git checkout #{@merge_request.target_branch}
- git merge --no-ff #{@merge_request.source_project_path}-#{@merge_request.source_branch}
+ git checkout #{h @merge_request.target_branch}
+ git merge --no-ff #{h @merge_request.source_project_path}-#{h @merge_request.source_branch}
- else
:preserve
- git checkout #{@merge_request.target_branch}
- git merge --no-ff #{@merge_request.source_branch}
+ git checkout #{h @merge_request.target_branch}
+ git merge --no-ff #{h @merge_request.source_branch}
%p
%strong Step 4.
Push the result of the merge to GitLab
%pre.dark
:preserve
- git push origin #{@merge_request.target_branch}
+ git push origin #{h @merge_request.target_branch}
- unless @merge_request.can_be_merged_by?(current_user)
%p
Note that pushing to GitLab requires write access to this repository.
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml
index e3cd4346872..b4f62a75890 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -1,11 +1,12 @@
-%h2.issue-title
- = gfm escape_once(@merge_request.title)
+.gray-content-block.middle-block
+ %h2.issue-title
+ = gfm escape_once(@merge_request.title)
-%div
- - if @merge_request.description.present?
- .description{class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : ''}
- .wiki
- = preserve do
- = markdown(@merge_request.description)
- %textarea.hidden.js-task-list-field
- = @merge_request.description
+ %div
+ - if @merge_request.description.present?
+ .description{class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : ''}
+ .wiki
+ = preserve do
+ = markdown(@merge_request.description)
+ %textarea.hidden.js-task-list-field
+ = @merge_request.description
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index 9a1eb36fc88..2bf9cd597a4 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,10 +1,11 @@
-%h4.page-title
+.page-title
.issue-box{ class: issue_box_class(@merge_request) }
= @merge_request.state_human_name
- Merge Request ##{@merge_request.iid}
- %small.creator
+ %span.issue-id Merge Request ##{@merge_request.iid}
+ %span.creator
+ &middot;
+ created by #{link_to_member(@project, @merge_request.author, size: 24)}
&middot;
- created by #{link_to_member(@project, @merge_request.author)}
= time_ago_with_tooltip(@merge_request.created_at)
- if @merge_request.updated_at != @merge_request.created_at
%span
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index 4d4e2f68f61..68dda1424cf 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -1,29 +1,44 @@
- if @merge_request.has_ci?
- .mr-widget-heading
- - [:success, :skipped, :canceled, :failed, :running, :pending].each do |status|
- .ci_widget{class: "ci-#{status}", style: "display:none"}
- - if status == :success
- - status = "passed"
- = icon("check-circle")
- - else
- = icon("circle")
+ - ci_commit = @merge_request.source_project.ci_commit(@merge_request.source_sha)
+ - if ci_commit
+ - status = ci_commit.status
+ .mr-widget-heading
+ .ci_widget{class: "ci-#{status}"}
+ = ci_status_icon(ci_commit)
%span CI build #{status}
for #{@merge_request.last_commit_short_sha}.
%span.ci-coverage
- = link_to "View build details", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
+ = link_to "View build details", ci_status_path(ci_commit)
- .ci_widget
- = icon("spinner spin")
- Checking CI status for #{@merge_request.last_commit_short_sha}&hellip;
+ - else
+ - # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX
+ - # Remove in later versions when services like Jenkins will set CI status via Commit status API
+ .mr-widget-heading
+ - [:success, :skipped, :canceled, :failed, :running, :pending].each do |status|
+ .ci_widget{class: "ci-#{status}", style: "display:none"}
+ - if status == :success
+ - status = "passed"
+ = icon("check-circle")
+ - else
+ = icon("circle")
+ %span CI build #{status}
+ for #{@merge_request.last_commit_short_sha}.
+ %span.ci-coverage
+ - if ci_build_details_path(@merge_request)
+ = link_to "View build details", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink"
- .ci_widget.ci-not_found{style: "display:none"}
- = icon("times-circle")
- Could not find CI status for #{@merge_request.last_commit_short_sha}.
+ .ci_widget
+ = icon("spinner spin")
+ Checking CI status for #{@merge_request.last_commit_short_sha}&hellip;
- .ci_widget.ci-error{style: "display:none"}
- = icon("times-circle")
- Could not connect to the CI server. Please check your settings and try again.
+ .ci_widget.ci-not_found{style: "display:none"}
+ = icon("times-circle")
+ Could not find CI status for #{@merge_request.last_commit_short_sha}.
- :coffeescript
- $ ->
- merge_request_widget.getCiStatus()
+ .ci_widget.ci-error{style: "display:none"}
+ = icon("times-circle")
+ Could not connect to the CI server. Please check your settings and try again.
+
+ :coffeescript
+ $ ->
+ merge_request_widget.getCiStatus()
diff --git a/app/views/projects/merge_requests/widget/_merged.html.haml b/app/views/projects/merge_requests/widget/_merged.html.haml
index d22dfa085b8..f223f687def 100644
--- a/app/views/projects/merge_requests/widget/_merged.html.haml
+++ b/app/views/projects/merge_requests/widget/_merged.html.haml
@@ -20,7 +20,7 @@
The changes were merged into
%span.label-branch= @merge_request.target_branch
You can remove the source branch now.
- = link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @source_branch), remote: true, method: :delete, class: "btn btn-primary btn-sm remove_source_branch" do
+ = link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request.source_branch), remote: true, method: :delete, class: "btn btn-primary btn-sm remove_source_branch" do
%i.fa.fa-times
Remove Source Branch
diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml
index b61e193fc42..613525437ab 100644
--- a/app/views/projects/merge_requests/widget/open/_accept.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml
@@ -9,7 +9,7 @@
= label_tag :should_remove_source_branch, class: "remove_source_checkbox" do
= check_box_tag :should_remove_source_branch
Remove source branch
- .accept-control
+ .accept-control.right
= link_to "#", class: "modify-merge-commit-link js-toggle-button" do
= icon('edit')
Modify commit message
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index b93462e5bdf..74e9668052d 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -21,7 +21,7 @@
.form-group.milestone-description
= f.label :description, "Description", class: "control-label"
.col-sm-10
- = render layout: 'projects/md_preview', locals: { preview_class: "wiki" } do
+ = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
= render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
.hint
.pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}.
diff --git a/app/views/projects/milestones/_header_title.html.haml b/app/views/projects/milestones/_header_title.html.haml
new file mode 100644
index 00000000000..5f4b6982a6d
--- /dev/null
+++ b/app/views/projects/milestones/_header_title.html.haml
@@ -0,0 +1 @@
+- header_title project_title(@project, "Milestones", namespace_project_milestones_path(@project.namespace, @project))
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 2ce5358fa74..5e93d55b1fb 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -1,28 +1,34 @@
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) }
- .pull-right
- - if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
- = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-sm edit-milestone-link btn-grouped" do
- %i.fa.fa-pencil-square-o
- Edit
- = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-sm btn-close"
- = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-sm btn-remove" do
- %i.fa.fa-trash-o
- Remove
+ .row
+ .col-sm-6
+ %strong
+ = link_to_gfm truncate(milestone.title, length: 100), namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
- %h4
- = link_to_gfm truncate(milestone.title, length: 100), namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
- - if milestone.expired? and not milestone.closed?
- %span.cred (Expired)
- %small
- = milestone.expires_at
+ .col-sm-6
+ .pull-right.light #{milestone.percent_complete}% complete
.row
.col-sm-6
= link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do
= pluralize milestone.issues.count, 'Issue'
- &nbsp;
+ &middot;
= link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do
= pluralize milestone.merge_requests.count, 'Merge Request'
- &nbsp;
- %span.light #{milestone.percent_complete}% complete
.col-sm-6
= milestone_progress_bar(milestone)
+
+ .row
+ .col-sm-6
+ - if milestone.expired? and not milestone.closed?
+ %span.cred (Expired)
+ - if milestone.expires_at
+ %span
+ = milestone.expires_at
+ .col-sm-6
+ - if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
+ = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs edit-milestone-link btn-grouped" do
+ %i.fa.fa-pencil-square-o
+ Edit
+ = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close"
+ = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do
+ %i.fa.fa-trash-o
+ Remove
diff --git a/app/views/projects/milestones/edit.html.haml b/app/views/projects/milestones/edit.html.haml
index c09815a212a..e9dc0b77462 100644
--- a/app/views/projects/milestones/edit.html.haml
+++ b/app/views/projects/milestones/edit.html.haml
@@ -1,2 +1,3 @@
- page_title "Edit", @milestone.title, "Milestones"
+= render "header_title"
= render "form"
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index 995eecd7830..a207385bd43 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -1,18 +1,22 @@
- page_title "Milestones"
-.pull-right
- - if can? current_user, :admin_milestone, @project
- = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "pull-right btn btn-new", title: "New Milestone" do
- %i.fa.fa-plus
- New Milestone
+= render "header_title"
= render 'shared/milestones_filter'
+.gray-content-block
+ .pull-right
+ - if can? current_user, :admin_milestone, @project
+ = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "pull-right btn btn-new", title: "New Milestone" do
+ %i.fa.fa-plus
+ New Milestone
+ .oneline
+ Milestone allows you to group issues and set due date for it
+
.milestones
- .panel.panel-default
- %ul.well-list
- = render @milestones
+ %ul.content-list
+ = render @milestones
- - if @milestones.blank?
- %li
- .nothing-here-block No milestones to show
+ - if @milestones.blank?
+ %li
+ .nothing-here-block No milestones to show
= paginate @milestones, theme: "gitlab"
diff --git a/app/views/projects/milestones/new.html.haml b/app/views/projects/milestones/new.html.haml
index 47149dfea41..9ba9acb6f77 100644
--- a/app/views/projects/milestones/new.html.haml
+++ b/app/views/projects/milestones/new.html.haml
@@ -1,2 +1,3 @@
- page_title "New Milestone"
+= render "header_title"
= render "form"
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 7b1681df336..4eeb0621e52 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -1,4 +1,6 @@
- page_title @milestone.title, "Milestones"
+= render "header_title"
+
%h4.page-title
.issue-box{ class: issue_box_class(@milestone) }
- if @milestone.closed?
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index 52b5b8b877e..16005161df6 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -1,4 +1,5 @@
- page_title "Network", @ref
+= header_title project_title(@project, "Network", namespace_project_network_path(@project.namespace, @project, current_ref))
= render "head"
.project-network
.controls
diff --git a/app/views/projects/network/show.json.erb b/app/views/projects/network/show.json.erb
index dc82adcb2c6..122e84b41b2 100644
--- a/app/views/projects/network/show.json.erb
+++ b/app/views/projects/network/show.json.erb
@@ -9,7 +9,7 @@
author: {
name: c.author_name,
email: c.author_email,
- icon: avatar_icon(c.author_email, 20)
+ icon: image_path(avatar_icon(c.author_email, 20))
},
time: c.time,
space: c.spaces.first,
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 636218368cc..bccea21e7a8 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -72,6 +72,11 @@
%i.fa.fa-google
Google Code
+ - if fogbugz_import_enabled?
+ = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do
+ %i.fa.fa-bug
+ Fogbugz
+
- if git_import_enabled?
= link_to "#", class: 'btn js-toggle-button import_git' do
%i.fa.fa-git
diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml
index 8f7d2e84c70..a0e26f9827e 100644
--- a/app/views/projects/notes/_edit_form.html.haml
+++ b/app/views/projects/notes/_edit_form.html.haml
@@ -1,7 +1,7 @@
.note-edit-form
= form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true do |f|
= note_target_fields(note)
- = render layout: 'projects/md_preview', locals: { preview_class: 'note-text' } do
+ = render layout: 'projects/md_preview', locals: { preview_class: 'md-preview' } do
= render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field'
= render 'projects/notes/hints'
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index 3be8f44b282..d99445da59a 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -7,7 +7,7 @@
= f.hidden_field :noteable_id
= f.hidden_field :noteable_type
- = render layout: 'projects/md_preview', locals: { preview_class: "note-text", referenced_users: true } do
+ = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
= render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text'
= render 'projects/notes/hints'
.error-alert
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index de75d44fc41..1638ad6891a 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -1,11 +1,8 @@
%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: { discussion: note.discussion_id } }
.timeline-entry-inner
.timeline-icon
- - if note.system
- %span= icon('circle')
- - else
- = link_to user_path(note.author) do
- = image_tag avatar_icon(note.author_email), class: 'avatar s40', alt: ''
+ = link_to user_path(note.author) do
+ = image_tag avatar_icon(note.author_email), class: 'avatar s40', alt: ''
.timeline-content
.note-header
- if note_editable?(note)
@@ -17,14 +14,10 @@
= icon('trash-o')
- unless note.system
- - member = note.project.team.find_member(note.author.id)
- - if member
+ - access = note.project.team.human_max_access(note.author.id)
+ - if access
%span.note-role.label
- = member.human_access
-
- - if note.system
- = link_to user_path(note.author) do
- = image_tag avatar_icon(note.author_email), class: 'avatar s16', alt: ''
+ = access
= link_to_member(note.project, note.author, avatar: false)
@@ -66,7 +59,9 @@
.note-text
= preserve do
= markdown(note.note, {no_header_anchors: true})
- = render 'projects/notes/edit_form', note: note
+ - unless note.system?
+ -# System notes can't be edited
+ = render 'projects/notes/edit_form', note: note
- if note.attachment.url
.note-attachment
diff --git a/app/views/projects/project_members/_header_title.html.haml b/app/views/projects/project_members/_header_title.html.haml
new file mode 100644
index 00000000000..a31f0a37fa2
--- /dev/null
+++ b/app/views/projects/project_members/_header_title.html.haml
@@ -0,0 +1 @@
+- header_title project_title(@project, "Members", namespace_project_project_members_path(@project.namespace, @project))
diff --git a/app/views/projects/project_members/import.html.haml b/app/views/projects/project_members/import.html.haml
index 6914543f6da..189906498cb 100644
--- a/app/views/projects/project_members/import.html.haml
+++ b/app/views/projects/project_members/import.html.haml
@@ -1,4 +1,6 @@
- page_title "Import members"
+= render "header_title"
+
%h3.page-title
Import members from another project
%p.light
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 162583e4b1d..9a0a824b811 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,30 +1,29 @@
- page_title "Members"
-%h3.page-title
- Users with access to this project
-
-%p.light
+= render "header_title"
+
+.gray-content-block.top-block
+ .clearfix.js-toggle-container
+ = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
+ .form-group
+ = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input' }
+ = button_tag 'Search', class: 'btn'
+
+ - if can?(current_user, :admin_project_member, @project)
+ %span.pull-right
+ = button_tag class: 'btn btn-new btn-grouped js-toggle-button', type: 'button' do
+ Add members
+ %i.fa.fa-chevron-down
+ = link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do
+ Import members
+
+ .js-toggle-content.hide.new-group-member-holder
+ = render "new_project_member"
+
+%p.prepend-top-default.light
+ Users with access to this project are listed below.
Read more about project permissions
%strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
-%hr
-
-.clearfix.js-toggle-container
- = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
- .form-group
- = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input' }
- = button_tag 'Search', class: 'btn'
-
- - if can?(current_user, :admin_project_member, @project)
- %span.pull-right
- = button_tag class: 'btn btn-new btn-grouped js-toggle-button', type: 'button' do
- Add members
- %i.fa.fa-chevron-down
- = link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do
- Import members
-
- .js-toggle-content.hide.new-group-member-holder
- = render "new_project_member"
-
= render "team", members: @project_members
- if @group
diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml
new file mode 100644
index 00000000000..e6b8a2e6fe7
--- /dev/null
+++ b/app/views/projects/runners/_runner.html.haml
@@ -0,0 +1,34 @@
+%li.runner{id: dom_id(runner)}
+ %h4
+ = runner_status_icon(runner)
+ %span.monospace
+ - if @runners.include?(runner)
+ = link_to runner.short_sha, runner_path(runner)
+ %small
+ =link_to edit_namespace_project_runner_path(@project.namespace, @project, runner) do
+ %i.fa.fa-edit.btn
+ - else
+ = runner.short_sha
+
+ .pull-right
+ - if @runners.include?(runner)
+ - if runner.belongs_to_one_project?
+ = link_to 'Remove runner', runner_path(runner), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
+ - else
+ - runner_project = @ci_project.runner_projects.find_by(runner_id: runner)
+ = link_to 'Disable for this project', [:ci, @ci_project, runner_project], data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
+ - elsif runner.specific?
+ = form_for [:ci, @ci_project, @ci_project.runner_projects.new] do |f|
+ = f.hidden_field :runner_id, value: runner.id
+ = f.submit 'Enable for this project', class: 'btn btn-sm'
+ .pull-right
+ %small.light
+ \##{runner.id}
+ - if runner.description.present?
+ %p.runner-description
+ = runner.description
+ - if runner.tag_list.present?
+ %p
+ - runner.tag_list.each do |tag|
+ %span.label.label-primary
+ = tag
diff --git a/app/views/projects/runners/_shared_runners.html.haml b/app/views/projects/runners/_shared_runners.html.haml
new file mode 100644
index 00000000000..316ea747b14
--- /dev/null
+++ b/app/views/projects/runners/_shared_runners.html.haml
@@ -0,0 +1,23 @@
+%h3 Shared runners
+
+.bs-callout.bs-callout-warning
+ GitLab Runners do not offer secure isolation between projects that they do builds for. You are TRUSTING all GitLab users who can push code to project A, B or C to run shell scripts on the machine hosting runner X.
+ %hr
+ - if @ci_project.shared_runners_enabled
+ = link_to toggle_shared_runners_ci_project_path(@ci_project), class: 'btn btn-warning', method: :post do
+ Disable shared runners
+ - else
+ = link_to toggle_shared_runners_ci_project_path(@ci_project), class: 'btn btn-success', method: :post do
+ Enable shared runners
+ &nbsp; for this project
+
+- if @shared_runners_count.zero?
+ This application has no shared runners yet.
+ Please use specific runners or ask administrator to create one
+- else
+ %h4.underlined-title Available shared runners - #{@shared_runners_count}
+ %ul.bordered-list.available-shared-runners
+ = render partial: 'runner', collection: @shared_runners, as: :runner
+ - if @shared_runners_count > 10
+ .light
+ and #{@shared_runners_count - 10} more...
diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml
new file mode 100644
index 00000000000..c13625c7e49
--- /dev/null
+++ b/app/views/projects/runners/_specific_runners.html.haml
@@ -0,0 +1,29 @@
+%h3 Specific runners
+
+.bs-callout.help-callout
+ %h4 How to setup a new project specific runner
+
+ %ol
+ %li
+ Install GitLab Runner software.
+ Checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} to install it
+ %li
+ Specify following URL during runner setup:
+ %code #{ci_root_url(only_path: false)}
+ %li
+ Use the following registration token during setup:
+ %code #{@ci_project.token}
+ %li
+ Start runner!
+
+
+- if @runners.any?
+ %h4.underlined-title Runners activated for this project
+ %ul.bordered-list.activated-specific-runners
+ = render partial: 'runner', collection: @runners, as: :runner
+
+- if @specific_runners.any?
+ %h4.underlined-title Available specific runners
+ %ul.bordered-list.available-specific-runners
+ = render partial: 'runner', collection: @specific_runners, as: :runner
+ = paginate @specific_runners
diff --git a/app/views/projects/runners/edit.html.haml b/app/views/projects/runners/edit.html.haml
new file mode 100644
index 00000000000..66851d38316
--- /dev/null
+++ b/app/views/projects/runners/edit.html.haml
@@ -0,0 +1,27 @@
+%h4 Runner ##{@runner.id}
+%hr
+= form_for @runner, url: runner_path(@runner), html: { class: 'form-horizontal' } do |f|
+ .form-group
+ = label :active, "Active", class: 'control-label'
+ .col-sm-10
+ .checkbox
+ = f.check_box :active
+ %span.light Paused runners don't accept new builds
+ .form-group
+ = label_tag :token, class: 'control-label' do
+ Token
+ .col-sm-10
+ = f.text_field :token, class: 'form-control', readonly: true
+ .form-group
+ = label_tag :description, class: 'control-label' do
+ Description
+ .col-sm-10
+ = f.text_field :description, class: 'form-control'
+ .form-group
+ = label_tag :tag_list, class: 'control-label' do
+ Tags
+ .col-sm-10
+ = f.text_field :tag_list, class: 'form-control'
+ .help-block You can setup jobs to only use runners with specific tags
+ .form-actions
+ = f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/projects/runners/index.html.haml b/app/views/projects/runners/index.html.haml
new file mode 100644
index 00000000000..529fb9c296d
--- /dev/null
+++ b/app/views/projects/runners/index.html.haml
@@ -0,0 +1,25 @@
+.light
+ %p
+ A 'runner' is a process which runs a build.
+ You can setup as many runners as you need.
+ %br
+ Runners can be placed on separate users, servers, and even on your local machine.
+
+ %p Each runner can be in one of the following states:
+ %div
+ %ul
+ %li
+ %span.label.label-success active
+ \- runner is active and can process any new build
+ %li
+ %span.label.label-danger paused
+ \- runner is paused and will not receive any new build
+
+%hr
+
+%p.lead To start serving your builds you can either add specific runners to your project or use shared runners
+.row
+ .col-sm-6
+ = render 'specific_runners'
+ .col-sm-6
+ = render 'shared_runners'
diff --git a/app/views/projects/runners/show.html.haml b/app/views/projects/runners/show.html.haml
new file mode 100644
index 00000000000..ffec495f85a
--- /dev/null
+++ b/app/views/projects/runners/show.html.haml
@@ -0,0 +1,64 @@
+= content_for :title do
+ %h3.project-title
+ Runner ##{@runner.id}
+ .pull-right
+ - if @runner.shared?
+ %span.runner-state.runner-state-shared
+ Shared
+ - else
+ %span.runner-state.runner-state-specific
+ Specific
+
+%table.table
+ %thead
+ %tr
+ %th Property Name
+ %th Value
+ %tr
+ %td
+ Tags
+ %td
+ - @runner.tag_list.each do |tag|
+ %span.label.label-primary
+ = tag
+ %tr
+ %td
+ Name
+ %td
+ = @runner.name
+ %tr
+ %td
+ Version
+ %td
+ = @runner.version
+ %tr
+ %td
+ Revision
+ %td
+ = @runner.revision
+ %tr
+ %td
+ Platform
+ %td
+ = @runner.platform
+ %tr
+ %td
+ Architecture
+ %td
+ = @runner.architecture
+ %tr
+ %td
+ Description
+ %td
+ = @runner.description
+ %tr
+ %td
+ Last contact
+ %td
+ - if @runner.contacted_at
+ #{time_ago_in_words(@runner.contacted_at)} ago
+ - else
+ Never
+
+
+
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 507f2c7beb0..efa119edd5a 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -11,10 +11,10 @@
= render "home_panel"
-.project-stats
+.project-stats.gray-content-block
%ul.nav.nav-pills
%li
- = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref) do
+ = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
= pluralize(number_with_delimiter(@project.commit_count), 'commit')
%li
= link_to namespace_project_branches_path(@project.namespace, @project) do
@@ -24,7 +24,7 @@
= pluralize(number_with_delimiter(@repository.tag_names.count), 'tag')
%li
- = link_to namespace_project_path(@project.namespace, @project) do
+ = link_to project_files_path(@project) do
= repository_size
- if !prefer_readme? && @repository.readme
@@ -63,21 +63,22 @@
= icon("exclamation-triangle fw")
Archived project! Repository is read-only
-%hr
%section
- if prefer_readme?
- = render 'projects/readme'
+ .project-show-readme
+ = render 'projects/readme'
- else
- = render 'projects/activity'
+ .project-show-activity
+ = render 'projects/activity'
- if current_user
- access = user_max_access_in_project(current_user, @project)
- if access
- %hr
- %p.light
- You have #{access} access to this project.
- - if @project.project_member_by_id(current_user)
- = link_to leave_namespace_project_project_members_path(@project.namespace, @project),
- data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project', class: 'cred' do
- Leave this project \ No newline at end of file
+ .prepend-top-20.project-footer
+ .gray-content-block.footer-block.center
+ You have #{access} access to this project.
+ - if @project.project_member_by_id(current_user)
+ = link_to leave_namespace_project_project_members_path(@project.namespace, @project),
+ data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project', class: 'cred' do
+ Leave this project
diff --git a/app/views/projects/snippets/_header_title.html.haml b/app/views/projects/snippets/_header_title.html.haml
new file mode 100644
index 00000000000..04f0bbe9853
--- /dev/null
+++ b/app/views/projects/snippets/_header_title.html.haml
@@ -0,0 +1 @@
+- header_title project_title(@project, "Snippets", namespace_project_snippets_path(@project.namespace, @project))
diff --git a/app/views/projects/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml
index 945f0084dff..e69f2d99709 100644
--- a/app/views/projects/snippets/edit.html.haml
+++ b/app/views/projects/snippets/edit.html.haml
@@ -1,4 +1,6 @@
- page_title "Edit", @snippet.title, "Snippets"
+= render "header_title"
+
%h3.page-title
Edit snippet
%hr
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index 45d4de6a385..3fed2c9949d 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -1,4 +1,6 @@
- page_title "Snippets"
+= render "header_title"
+
%h3.page-title
Snippets
- if can? current_user, :create_project_snippet, @project
diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml
index e38d95c45e7..67cd69fd215 100644
--- a/app/views/projects/snippets/new.html.haml
+++ b/app/views/projects/snippets/new.html.haml
@@ -1,4 +1,6 @@
- page_title "New Snippets"
+= render "header_title"
+
%h3.page-title
New snippet
%hr
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index 8cbb813c758..be7d4d486fa 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -1,4 +1,6 @@
- page_title @snippet.title, "Snippets"
+= render "header_title"
+
%h3.page-title
= @snippet.title
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index 28ad272322f..2ca295fc5f3 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -1,13 +1,14 @@
- commit = @repository.commit(tag.target)
%li
- %h4
+ %div
= link_to namespace_project_commits_path(@project.namespace, @project, tag.name), class: "" do
- %i.fa.fa-tag
- = tag.name
+ %strong
+ %i.fa.fa-tag
+ = tag.name
- if tag.message.present?
&nbsp;
= strip_gpg_signature(tag.message)
- .pull-right
+ .controls
- if can? current_user, :download_code, @project
= render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-xs'
- if can?(current_user, :admin_project, @project)
@@ -15,8 +16,7 @@
%i.fa.fa-trash-o
- if commit
- %ul.list-unstyled
- = render 'projects/commits/inline_commit', commit: commit, project: @project
+ = render 'projects/branches/commit', commit: commit, project: @project
- else
%p
Cant find HEAD commit for this tag
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index d4652a47cba..85d76eae3b5 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -1,21 +1,19 @@
- page_title "Tags"
+= render "projects/commits/header_title"
= render "projects/commits/head"
-%h3.page-title
- Git Tags
+.gray-content-block
- if can? current_user, :push_code, @project
.pull-right
= link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do
%i.fa.fa-add-sign
New tag
-
-%p.light
- Tags give the ability to mark specific points in history as being important
-%hr
+ .oneline
+ Tags give the ability to mark specific points in history as being important
.tags
- unless @tags.empty?
- %ul.bordered-list
+ %ul.content-list
- @tags.each do |tag|
= render 'tag', tag: @repository.find_tag(tag)
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index 172fafdeeff..9f5c1be125c 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -1,4 +1,6 @@
- page_title "New Tag"
+= render "projects/commits/header_title"
+
- if @error
.alert.alert-danger
%button{ type: "button", class: "close", "data-dismiss" => "alert"} &times;
diff --git a/app/views/projects/tree/_tree.html.haml b/app/views/projects/tree/_tree.html.haml
index 5048154cb2f..367a87927d7 100644
--- a/app/views/projects/tree/_tree.html.haml
+++ b/app/views/projects/tree/_tree.html.haml
@@ -14,7 +14,7 @@
%small
%i.fa.fa-plus
-%div#tree-content-holder.tree-content-holder
+%div#tree-content-holder.tree-content-holder.prepend-top-20
%table#tree-slider{class: "table_#{@hex_path} tree-table" }
%thead
%tr
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index c9e59428e78..dec4677f830 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -1,4 +1,5 @@
- page_title @path.presence || "Files", @ref
+- header_title project_title(@project, "Files", project_files_path(@project))
= content_for :meta_tags do
- if current_user
= auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits")
diff --git a/app/views/projects/triggers/_trigger.html.haml b/app/views/projects/triggers/_trigger.html.haml
new file mode 100644
index 00000000000..48b3b5c9920
--- /dev/null
+++ b/app/views/projects/triggers/_trigger.html.haml
@@ -0,0 +1,14 @@
+%tr
+ %td
+ .clearfix
+ %span.monospace= trigger.token
+
+ %td
+ - if trigger.last_trigger_request
+ #{time_ago_in_words(trigger.last_trigger_request.created_at)} ago
+ - else
+ Never
+
+ %td
+ .pull-right
+ = link_to 'Revoke', namespace_project_trigger_path(@project.namespace, @project, trigger), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-danger btn-sm btn-grouped"
diff --git a/app/views/projects/triggers/index.html.haml b/app/views/projects/triggers/index.html.haml
new file mode 100644
index 00000000000..17dcb78e256
--- /dev/null
+++ b/app/views/projects/triggers/index.html.haml
@@ -0,0 +1,67 @@
+%h3.page-title
+ Triggers
+
+%p.light
+ Triggers can be used to force a rebuild of a specific branch or tag with an API call.
+
+%hr.clearfix
+
+-if @triggers.any?
+ %table.table
+ %thead
+ %th Token
+ %th Last used
+ %th
+ = render partial: 'trigger', collection: @triggers, as: :trigger
+- else
+ %h4 No triggers
+
+= form_for @trigger, url: url_for(controller: 'projects/triggers', action: 'create'), html: { class: 'form-horizontal' } do |f|
+ .clearfix
+ = f.submit "Add Trigger", class: 'btn btn-success pull-right'
+
+%hr.clearfix
+
+-if @triggers.any?
+ %h3
+ Use CURL
+
+ %p.light
+ Copy the token above and set your branch or tag name. This is the reference that will be rebuild.
+
+
+ %pre
+ :plain
+ curl -X POST \
+ -F token=TOKEN \
+ #{ci_build_trigger_url(@ci_project.id, 'REF_NAME')}
+ %h3
+ Use .gitlab-ci.yml
+
+ %p.light
+ Copy the snippet to
+ %i .gitlab-ci.yml
+ of dependent project.
+ At the end of your build it will trigger this project to rebuilt.
+
+ %pre
+ :plain
+ trigger:
+ type: deploy
+ script:
+ - "curl -X POST -F token=TOKEN #{ci_build_trigger_url(@ci_project.id, 'REF_NAME')}"
+ %h3
+ Pass build variables
+
+ %p.light
+ Add
+ %strong variables[VARIABLE]=VALUE
+ to API request.
+ The value of variable could then be used to distinguish triggered build from normal one.
+
+ %pre
+ :plain
+ curl -X POST \
+ -F token=TOKEN \
+ -F "variables[RUN_NIGHTLY_BUILD]=true" \
+ #{ci_build_trigger_url(@ci_project.id, 'REF_NAME')}
diff --git a/app/views/projects/variables/show.html.haml b/app/views/projects/variables/show.html.haml
new file mode 100644
index 00000000000..29416a94ff6
--- /dev/null
+++ b/app/views/projects/variables/show.html.haml
@@ -0,0 +1,39 @@
+%h3.page-title
+ Secret Variables
+
+%p.light
+ These variables will be set to environment by the runner and will be hidden in the build log.
+ %br
+ So you can use them for passwords, secret keys or whatever you want.
+
+%hr
+
+
+= nested_form_for @ci_project, url: url_for(controller: 'projects/variables', action: 'update'), html: { class: 'form-horizontal' } do |f|
+ - if @project.errors.any?
+ #error_explanation
+ %p.lead= "#{pluralize(@ci_project.errors.count, "error")} prohibited this project from being saved:"
+ .alert.alert-error
+ %ul
+ - @ci_project.errors.full_messages.each do |msg|
+ %li= msg
+
+ = f.fields_for :variables do |variable_form|
+ .form-group
+ = variable_form.label :key, 'Key', class: 'control-label'
+ .col-sm-10
+ = variable_form.text_field :key, class: 'form-control', placeholder: "PROJECT_VARIABLE"
+
+ .form-group
+ = variable_form.label :value, 'Value', class: 'control-label'
+ .col-sm-10
+ = variable_form.text_area :value, class: 'form-control', rows: 2, placeholder: ""
+
+ = variable_form.link_to_remove "Remove this variable", class: 'btn btn-danger pull-right prepend-top-10'
+ %hr
+ %p
+ .clearfix
+ = f.link_to_add "Add a variable", :variables, class: 'btn btn-success pull-right'
+
+ .form-actions
+ = f.submit 'Save changes', class: 'btn btn-save', return_to: request.original_url
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 904600499ae..05d754adbe5 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -21,7 +21,7 @@
.form-group.wiki-content
= f.label :content, class: 'control-label'
.col-sm-10
- = render layout: 'projects/md_preview', locals: { preview_class: "wiki" } do
+ = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
= render 'projects/zen', f: f, attr: :content, classes: 'description form-control'
.col-sm-12.hint
.pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}
diff --git a/app/views/projects/wikis/_header_title.html.haml b/app/views/projects/wikis/_header_title.html.haml
new file mode 100644
index 00000000000..408adc36ca6
--- /dev/null
+++ b/app/views/projects/wikis/_header_title.html.haml
@@ -0,0 +1 @@
+- header_title project_title(@project, 'Wiki', get_project_wiki_path(@project))
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 788bb8cf1e2..14f25822259 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -1,8 +1,15 @@
%span.pull-right
+ - if can?(current_user, :create_wiki, @project)
+ = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new btn-grouped", "data-toggle" => "modal" do
+ %i.fa.fa-plus
+ New Page
+
- if (@page && @page.persisted?)
- = link_to history_namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
+ = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
Page History
- if can?(current_user, :create_wiki, @project)
- = link_to edit_namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
+ = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn btn-grouped" do
%i.fa.fa-pencil-square-o
Edit
+
+= render 'projects/wikis/new'
diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml
index 804a1b52dbe..fffb4eb31ab 100644
--- a/app/views/projects/wikis/_nav.html.haml
+++ b/app/views/projects/wikis/_nav.html.haml
@@ -1,19 +1,10 @@
-%ul.nav.nav-tabs
+%ul.center-top-menu
= nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
= link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
= nav_link(path: 'wikis#pages') do
- = link_to 'Pages', pages_namespace_project_wikis_path(@project.namespace, @project)
+ = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project)
= nav_link(path: 'wikis#git_access') do
- = link_to git_access_namespace_project_wikis_path(@project.namespace, @project) do
- %i.fa.fa-download
+ = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
Git Access
-
- - if can?(current_user, :create_wiki, @project)
- .pull-right
- = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
- %i.fa.fa-plus
- New Page
-
-= render 'projects/wikis/new'
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index 3f1dce1050c..0b709c3695b 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -1,4 +1,6 @@
- page_title "Edit", @page.title, "Wiki"
+= render "header_title"
+
= render 'nav'
.pull-right
= render 'main_links'
diff --git a/app/views/projects/wikis/empty.html.haml b/app/views/projects/wikis/empty.html.haml
index ead99412406..c7e490c3cd1 100644
--- a/app/views/projects/wikis/empty.html.haml
+++ b/app/views/projects/wikis/empty.html.haml
@@ -1,4 +1,6 @@
- page_title "Wiki"
+= render "header_title"
+
%h3.page-title Empty page
%hr
.error_message
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
index 825f2a161c4..6417ef4a38b 100644
--- a/app/views/projects/wikis/git_access.html.haml
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -1,15 +1,18 @@
- page_title "Git Access", "Wiki"
+= render "header_title"
+
= render 'nav'
-.row
- .col-sm-6
- %h3.page-title
- Git access for
- %strong= @project_wiki.path_with_namespace
+.gray-content-block
+ .row
+ .col-sm-6
+ %h3.page-title
+ Git access for
+ %strong= @project_wiki.path_with_namespace
- .col-sm-6
- = render "shared/clone_panel", project: @project_wiki
+ .col-sm-6
+ = render "shared/clone_panel", project: @project_wiki
-.git-empty
+.git-empty.prepend-top-default
%fieldset
%legend Install Gollum:
%pre.dark
@@ -20,7 +23,7 @@
%pre.dark
:preserve
git clone #{ content_tag(:span, default_url_to_repo(@project_wiki), class: 'clone')}
- cd #{@project_wiki.path}
+ cd #{h @project_wiki.path}
%legend Start Gollum And Edit Locally:
%pre.dark
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index 673ec2d20e5..bfbef823b35 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -1,8 +1,11 @@
-- page_title "History", @page.title, "Wiki"
+- page_title "History", @page.title.capitalize, "Wiki"
+= render "header_title"
+
= render 'nav'
-%h3.page-title
- %span.light History for
- = link_to @page.title, namespace_project_wiki_path(@project.namespace, @project, @page)
+.gray-content-block
+ %h3.page-title
+ %span.light History for
+ = link_to @page.title, namespace_project_wiki_path(@project.namespace, @project, @page)
%table.table
%thead
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index 890ff1aed73..03e6a522b25 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -1,8 +1,11 @@
- page_title "All Pages", "Wiki"
+= render "header_title"
+
= render 'nav'
-%h3.page-title
- All Pages
-%ul.bordered-list
+.gray-content-block
+ %h3.page-title
+ All Pages
+%ul.content-list
- @wiki_pages.each do |wiki_page|
%li
%h4
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 5c4dd7f91ae..55fbf5a8b6e 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -1,25 +1,28 @@
-- page_title @page.title, "Wiki"
+- page_title @page.title.capitalize, "Wiki"
+= render "header_title"
+
= render 'nav'
-%h3.page-title
- = @page.title
+
+.gray-content-block
= render 'main_links'
+ %h3.page-title
+ = @page.title.capitalize
-.wiki-last-edit-by
- Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
+ .wiki-last-edit-by
+ Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
- if @page.historical?
.warning_message
This is an old version of this page.
- You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", history_namespace_project_wiki_path(@project.namespace, @project, @page)}.
+ You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}.
-%hr
-.wiki-holder
+.wiki-holder.prepend-top-default
.wiki
= preserve do
= render_wiki_content(@page)
-%hr
-.wiki-last-edit-by
- Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
+.gray-content-block.footer-block
+ .wiki-last-edit-by
+ Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index 07672359dba..b23b2f0d5eb 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -4,7 +4,7 @@
.input-group-btn
%button{ |
type: 'button', |
- class: "btn btn-sm #{ 'active' if default_clone_protocol == 'ssh' }#{ ' has_tooltip' if current_user && current_user.require_ssh_key? }", |
+ class: "btn #{ 'active' if default_clone_protocol == 'ssh' }#{ ' has_tooltip' if current_user && current_user.require_ssh_key? }", |
:"data-clone" => project.ssh_url_to_repo, |
:"data-title" => "Add an SSH key to your profile<br> to pull or push via SSH",
:"data-html" => "true",
@@ -13,15 +13,15 @@
.input-group-btn
%button{ |
type: 'button', |
- class: "btn btn-sm #{ 'active' if default_clone_protocol == 'http' }#{ ' has_tooltip' if current_user && current_user.require_password? }", |
+ class: "btn #{ 'active' if default_clone_protocol == 'http' }#{ ' has_tooltip' if current_user && current_user.require_password? }", |
:"data-clone" => project.http_url_to_repo, |
:"data-title" => "Set a password on your account<br> to pull or push via #{gitlab_config.protocol.upcase}",
:"data-html" => "true",
:"data-container" => "body"}
= gitlab_config.protocol.upcase
- = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control input-sm", readonly: true
+ = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true
- if project.kind_of?(Project)
.input-group-addon
.visibility-level-label.has_tooltip{'data-title' => "#{visibility_level_label(project.visibility_level)} project" }
= visibility_level_icon(project.visibility_level)
- = visibility_level_label(project.visibility_level).downcase
+
diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml
index 334db60690d..8495774accc 100644
--- a/app/views/shared/_event_filter.html.haml
+++ b/app/views/shared/_event_filter.html.haml
@@ -1,4 +1,4 @@
-%ul.nav.nav-pills.event_filter
+.btn-group.btn-group-next.event-filter
= event_filter_link EventFilter.push, 'Push events'
= event_filter_link EventFilter.merged, 'Merge events'
= event_filter_link EventFilter.comments, 'Comments'
diff --git a/app/views/shared/_field.html.haml b/app/views/shared/_field.html.haml
index 45ec49280d2..8d6e16f74c3 100644
--- a/app/views/shared/_field.html.haml
+++ b/app/views/shared/_field.html.haml
@@ -8,7 +8,10 @@
- help = field[:help]
.form-group
- = form.label name, title, class: "control-label"
+ - if type == "password" && value.present?
+ = form.label name, "Change #{title}", class: "control-label"
+ - else
+ = form.label name, title, class: "control-label"
.col-sm-10
- if type == 'text'
= form.text_field name, class: "form-control", placeholder: placeholder
@@ -19,6 +22,6 @@
- elsif type == 'select'
= form.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
- elsif type == 'password'
- = form.password_field name, value: value, class: 'form-control'
+ = form.password_field name, autocomplete: "new-password", class: 'form-control'
- if help
%span.help-block= help
diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml
index f685ae7726c..cbdecda4fff 100644
--- a/app/views/shared/_milestones_filter.html.haml
+++ b/app/views/shared/_milestones_filter.html.haml
@@ -1,14 +1,11 @@
-.milestones-filters.append-bottom-10
- %ul.nav.nav-tabs
+.milestones-filters
+ %ul.center-top-menu
%li{class: ("active" if params[:state].blank? || params[:state] == 'opened')}
= link_to milestones_filter_path(state: 'opened') do
- %i.fa.fa-exclamation-circle
Open
%li{class: ("active" if params[:state] == 'closed')}
= link_to milestones_filter_path(state: 'closed') do
- %i.fa.fa-check-circle
Closed
%li{class: ("active" if params[:state] == 'all')}
= link_to milestones_filter_path(state: 'all') do
- %i.fa.fa-compass
All
diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml
index 229ae359bc5..a54c5fa8c33 100644
--- a/app/views/shared/groups/_group.html.haml
+++ b/app/views/shared/groups/_group.html.haml
@@ -1,23 +1,21 @@
- group_member = local_assigns[:group_member]
%li
- if group_member
- .pull-right.hidden-xs
+ .controls.hidden-xs
- if can?(current_user, :admin_group, group)
= link_to edit_group_path(group), class: "btn-sm btn btn-grouped" do
%i.fa.fa-cogs
- Settings
= link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Leave this group' do
%i.fa.fa-sign-out
- Leave
- = image_tag group_icon(group), class: "avatar s40 avatar-tile hidden-xs"
+ = image_tag group_icon(group), class: "avatar s46 hidden-xs"
= link_to group, class: 'group-name' do
%strong= group.name
- if group_member
as
- %strong #{group_member.human_access}
+ %span #{group_member.human_access}
%div.light
#{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")}
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index bcaa48c7a12..8f16773077e 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -1,33 +1,28 @@
.issues-filters
.issues-state-filters
- %ul.nav.nav-tabs
+ %ul.center-top-menu
%li{class: ("active" if params[:state] == 'opened')}
= link_to page_filter_path(state: 'opened') do
- = icon('exclamation-circle')
#{state_filters_text_for(:opened, @project)}
- if defined?(type) && type == :merge_requests
%li{class: ("active" if params[:state] == 'merged')}
= link_to page_filter_path(state: 'merged') do
- = icon('check-circle')
#{state_filters_text_for(:merged, @project)}
%li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed') do
- = icon('ban')
#{state_filters_text_for(:closed, @project)}
- else
%li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed') do
- = icon('check-circle')
#{state_filters_text_for(:closed, @project)}
%li{class: ("active" if params[:state] == 'all')}
= link_to page_filter_path(state: 'all') do
- = icon('compass')
#{state_filters_text_for(:all, @project)}
- .issues-details-filters
+ .issues-details-filters.gray-content-block
= form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name]), method: :get, class: 'filter-form' do
- if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project)
.check-all-holder
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 09327d645f3..33ec726e93c 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -9,7 +9,7 @@
= f.label :title, class: 'control-label' do
%strong= 'Title *'
.col-sm-10
- = f.text_field :title, maxlength: 255, autofocus: true,
+ = f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off',
class: 'form-control pad js-gfm-input', required: true
- if issuable.is_a?(MergeRequest)
@@ -24,7 +24,7 @@
= f.label :description, 'Description', class: 'control-label'
.col-sm-10
- = render layout: 'projects/md_preview', locals: { preview_class: "wiki", referenced_users: true } do
+ = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
= render 'projects/zen', f: f, attr: :description,
classes: 'description form-control'
.col-sm-12.hint
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index 021e3b689a1..16e1d8421de 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -1,14 +1,15 @@
- projects_limit = 20 unless local_assigns[:projects_limit]
- avatar = true unless local_assigns[:avatar] == false
- stars = true unless local_assigns[:stars] == false
+- ci = false unless local_assigns[:ci] == true
%ul.projects-list
- projects.each_with_index do |project, i|
- css_class = (i >= projects_limit) ? 'hide' : nil
= render "shared/projects/project", project: project,
- avatar: avatar, stars: stars, css_class: css_class
+ avatar: avatar, stars: stars, css_class: css_class, ci: ci
- - if projects.count > projects_limit
+ - if projects.size > projects_limit
%li.bottom.center
.light
#{projects_limit} of #{pluralize(projects.count, 'project')} displayed.
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 4bfdf4d55ff..e67e5a8a638 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -1,12 +1,14 @@
- avatar = true unless local_assigns[:avatar] == false
- stars = true unless local_assigns[:stars] == false
-- css_class = nil unless local_assigns[:css_class]
+- ci = false unless local_assigns[:ci] == true
+- css_class = '' unless local_assigns[:css_class]
+- css_class += " no-description" unless project.description.present?
%li.project-row{ class: css_class }
- = cache [project.namespace, project, controller.controller_name, controller.action_name, 'v2'] do
+ = cache [project.namespace, project, controller.controller_name, controller.action_name, 'v2.2'] do
= link_to project_path(project), class: dom_class(project) do
- if avatar
.dash-project-avatar
- = project_icon(project, alt: '', class: 'avatar project-avatar s40')
+ = project_icon(project, alt: '', class: 'avatar project-avatar s46')
%span.project-full-name
%span.namespace-name
- if project.namespace
@@ -14,8 +16,16 @@
\/
%span.project-name.filter-title
= project.name
+
+ .project-controls
+ - if ci && !project.empty_repo? && project.commit
+ - if ci_commit = project.ci_commit(project.commit.sha)
+ = link_to ci_status_path(ci_commit), class: "c#{ci_status_color(ci_commit)}",
+ title: "Build status: #{ci_commit.status}", data: {toggle: 'tooltip', placement: 'left'} do
+ = ci_status_icon(ci_commit)
+ &nbsp;
- if stars
- %span.pull-right.light
+ %span
%i.fa.fa-star
= project.star_count
- if project.description.present?
diff --git a/app/views/snippets/_head.html.haml b/app/views/snippets/_head.html.haml
deleted file mode 100644
index 0adf6b91f2c..00000000000
--- a/app/views/snippets/_head.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-%ul.center-top-menu
- = nav_link(page: user_snippets_path(current_user), html_options: {class: 'home'}) do
- = link_to user_snippets_path(current_user), title: 'Your snippets', data: {placement: 'right'} do
- Your Snippets
- = nav_link(page: snippets_path) do
- = link_to snippets_path, title: 'Explore snippets', data: {placement: 'right'} do
- Explore Snippets
diff --git a/app/views/snippets/current_user_index.html.haml b/app/views/snippets/current_user_index.html.haml
deleted file mode 100644
index 62a967a2e06..00000000000
--- a/app/views/snippets/current_user_index.html.haml
+++ /dev/null
@@ -1,35 +0,0 @@
-- page_title "Your Snippets"
-= render 'head'
-
-.slead
- Share code pastes with others out of git repository
-
- .pull-right
- = link_to new_snippet_path, class: "btn btn-new btn-sm", title: "New Snippet" do
- Add new snippet
-
-%ul.nav.nav-tabs
- = nav_tab :scope, nil do
- = link_to user_snippets_path(@user) do
- All
- %span.badge
- = @user.snippets.count
- = nav_tab :scope, 'are_private' do
- = link_to user_snippets_path(@user, scope: 'are_private') do
- Private
- %span.badge
- = @user.snippets.are_private.count
- = nav_tab :scope, 'are_internal' do
- = link_to user_snippets_path(@user, scope: 'are_internal') do
- Internal
- %span.badge
- = @user.snippets.are_internal.count
- = nav_tab :scope, 'are_public' do
- = link_to user_snippets_path(@user, scope: 'are_public') do
- Public
- %span.badge
- = @user.snippets.are_public.count
-
-.my-snippets
- = render 'snippets'
-
diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml
index 8608815e0a6..7e4918a6085 100644
--- a/app/views/snippets/index.html.haml
+++ b/app/views/snippets/index.html.haml
@@ -1,9 +1,13 @@
-- page_title "Public Snippets"
-- if current_user
- = render 'head'
+- page_title "By #{@user.name}", "Snippets"
-.slead
- Public snippets created by you and other users are listed here
+%ol.breadcrumb
+ %li
+ = link_to snippets_path do
+ Snippets
+ %li
+ = @user.name
+ .pull-right.hidden-xs
+ = link_to user_path(@user) do
+ #{@user.name} profile page
= render 'snippets'
-
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index aed00f9caeb..97374e073dc 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -20,10 +20,10 @@
.back-link
- if @snippet.author == current_user
- = link_to user_snippets_path(current_user) do
+ = link_to dashboard_snippets_path do
&larr; your snippets
- else
- = link_to snippets_path do
+ = link_to explore_snippets_path do
&larr; explore snippets
.file-holder
diff --git a/app/views/snippets/user_index.html.haml b/app/views/snippets/user_index.html.haml
deleted file mode 100644
index 7af5352da34..00000000000
--- a/app/views/snippets/user_index.html.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-- page_title "Snippets", @user.name
-
-%ol.breadcrumb
- %li
- = link_to snippets_path do
- Snippets
- %li
- = @user.name
- .pull-right.hidden-xs
- = link_to user_path(@user) do
- #{@user.name} profile page
-
-= render 'snippets'
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index aa4e8722fb1..11beb3e3239 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -16,17 +16,16 @@
- if @user == current_user
.pull-right.hidden-xs
= link_to profile_path, class: 'btn btn-sm' do
- %i.fa.fa-pencil-square-o
- Edit Profile settings
+ = icon('user')
+ Profile settings
- elsif current_user
- .pull-right
- %span.dropdown
- %a.light.dropdown-toggle.btn.btn-sm{href: '#', "data-toggle" => "dropdown"}
+ .report_abuse.pull-right
+ - if @user.abuse_report
+ %span#report_abuse_btn.light.btn.btn-sm.btn-close{title: 'Already reported for abuse', data: {toggle: 'tooltip', placement: 'right', container: 'body'}}
+ = icon('exclamation-circle')
+ - else
+ %a.light.btn.btn-sm{href: new_abuse_report_path(user_id: @user.id), title: 'Report abuse', data: {toggle: 'tooltip', placement: 'right', container: 'body'}}
= icon('exclamation-circle')
- %ul.dropdown-menu.dropdown-menu-right
- %li
- = link_to new_abuse_report_path(user_id: @user.id) do
- Report abuse
.username
@#{@user.username}
diff --git a/app/workers/ci/hip_chat_notifier_worker.rb b/app/workers/ci/hip_chat_notifier_worker.rb
new file mode 100644
index 00000000000..ebb43570e2a
--- /dev/null
+++ b/app/workers/ci/hip_chat_notifier_worker.rb
@@ -0,0 +1,19 @@
+module Ci
+ class HipChatNotifierWorker
+ include Sidekiq::Worker
+
+ def perform(message, options={})
+ room = options.delete('room')
+ token = options.delete('token')
+ server = options.delete('server')
+ name = options.delete('service_name')
+ client_opts = {
+ api_version: 'v2',
+ server_url: server
+ }
+
+ client = HipChat::Client.new(token, client_opts)
+ client[room].send(name, message, options.symbolize_keys)
+ end
+ end
+end
diff --git a/app/workers/ci/slack_notifier_worker.rb b/app/workers/ci/slack_notifier_worker.rb
new file mode 100644
index 00000000000..3bbb9b4bec7
--- /dev/null
+++ b/app/workers/ci/slack_notifier_worker.rb
@@ -0,0 +1,10 @@
+module Ci
+ class SlackNotifierWorker
+ include Sidekiq::Worker
+
+ def perform(webhook_url, message, options={})
+ notifier = Slack::Notifier.new(webhook_url)
+ notifier.ping(message, options)
+ end
+ end
+end
diff --git a/app/workers/ci/web_hook_worker.rb b/app/workers/ci/web_hook_worker.rb
new file mode 100644
index 00000000000..0bb83845572
--- /dev/null
+++ b/app/workers/ci/web_hook_worker.rb
@@ -0,0 +1,9 @@
+module Ci
+ class WebHookWorker
+ include Sidekiq::Worker
+
+ def perform(hook_id, data)
+ Ci::WebHook.find(hook_id).execute data
+ end
+ end
+end
diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb
index 8cfb96ef376..5a921a73fe9 100644
--- a/app/workers/email_receiver_worker.rb
+++ b/app/workers/email_receiver_worker.rb
@@ -4,7 +4,7 @@ class EmailReceiverWorker
sidekiq_options queue: :incoming_email
def perform(raw)
- return unless Gitlab::ReplyByEmail.enabled?
+ return unless Gitlab::IncomingEmail.enabled?
begin
Gitlab::Email::Receiver.new(raw).execute
diff --git a/app/workers/fork_registration_worker.rb b/app/workers/fork_registration_worker.rb
deleted file mode 100644
index fffa8b3a659..00000000000
--- a/app/workers/fork_registration_worker.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-class ForkRegistrationWorker
- include Sidekiq::Worker
-
- sidekiq_options queue: :default
-
- def perform(from_project_id, to_project_id, private_token)
- from_project = Project.find(from_project_id)
- to_project = Project.find(to_project_id)
-
- from_project.gitlab_ci_service.fork_registration(to_project, private_token)
- end
-end
diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb
new file mode 100644
index 00000000000..acd1c43f06b
--- /dev/null
+++ b/app/workers/repository_fork_worker.rb
@@ -0,0 +1,34 @@
+class RepositoryForkWorker
+ include Sidekiq::Worker
+ include Gitlab::ShellAdapter
+
+ sidekiq_options queue: :gitlab_shell
+
+ def perform(project_id, source_path, target_path)
+ project = Project.find_by_id(project_id)
+
+ unless project.present?
+ logger.error("Project #{project_id} no longer exists!")
+ return
+ end
+
+ result = gitlab_shell.fork_repository(source_path, target_path)
+
+ unless result
+ logger.error("Unable to fork project #{project_id} for repository #{source_path} -> #{target_path}")
+ project.import_fail
+ project.save
+ return
+ end
+
+ if project.valid_repo?
+ ProjectCacheWorker.perform_async(project.id)
+ project.import_finish
+ else
+ project.import_fail
+ logger.error("Project #{id} had an invalid repository after fork")
+ end
+
+ project.save
+ end
+end
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index f2ba2e15e7b..ea2808045eb 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -7,22 +7,31 @@ class RepositoryImportWorker
def perform(project_id)
project = Project.find(project_id)
- import_result = gitlab_shell.send(:import_repository,
+ unless project.import_url == Project::UNKNOWN_IMPORT_URL
+ import_result = gitlab_shell.send(:import_repository,
project.path_with_namespace,
project.import_url)
- return project.import_fail unless import_result
+ return project.import_fail unless import_result
+ else
+ unless project.create_repository
+ return project.import_fail
+ end
+ end
- data_import_result = if project.import_type == 'github'
- Gitlab::GithubImport::Importer.new(project).execute
- elsif project.import_type == 'gitlab'
- Gitlab::GitlabImport::Importer.new(project).execute
- elsif project.import_type == 'bitbucket'
- Gitlab::BitbucketImport::Importer.new(project).execute
- elsif project.import_type == 'google_code'
- Gitlab::GoogleCodeImport::Importer.new(project).execute
- else
- true
- end
+ data_import_result = case project.import_type
+ when 'github'
+ Gitlab::GithubImport::Importer.new(project).execute
+ when 'gitlab'
+ Gitlab::GitlabImport::Importer.new(project).execute
+ when 'bitbucket'
+ Gitlab::BitbucketImport::Importer.new(project).execute
+ when 'google_code'
+ Gitlab::GoogleCodeImport::Importer.new(project).execute
+ when 'fogbugz'
+ Gitlab::FogbugzImport::Importer.new(project).execute
+ else
+ true
+ end
return project.import_fail unless data_import_result
Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket'