Commit ee3714d5 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'ce-to-ee' into 'master'

CE to EE

See merge request !333
parents 63148e77 8dcffdcd
...@@ -6,6 +6,25 @@ v 7.9.0 (unreleased) ...@@ -6,6 +6,25 @@ v 7.9.0 (unreleased)
- Improve error messages for file edit failures - Improve error messages for file edit failures
- Improve UI for commits, issues and merge request lists - Improve UI for commits, issues and merge request lists
- Fix commit comments on first line of diff not rendering in Merge Request Discussion view. - Fix commit comments on first line of diff not rendering in Merge Request Discussion view.
- Improve trigger merge request hook when source project branch has been updated (Kirill Zaitsev)
- Save web edit in new branch
- Fix ordering of imported but unchanged projects (Marco Wessel)
- Mobile UI improvements: make aside content expandable
- Expose avatar_url in projects API
- Generalize image upload in drag and drop in markdown to all files (Hannes Rosenögger)
- Fix mass-unassignment of issues (Robert Speicher)
- Allow user confirmation to be skipped for new users via API
- Add a service to send updates to an Irker gateway (Romain Coltel)
v 7.8.1
- Fix run of custom post receive hooks
- Fix migration that caused issues when upgrading to version 7.8 from versions prior to 7.3
- Fix the warning for LDAP users about need to set password
- Fix avatars which were not shown for non logged in users
- Fix urls for the issues when relative url was enabled
- Add Bitbucket omniauth provider.
- Add Bitbucket importer.
- Support referencing issues to a project whose name starts with a digit
v 7.8.0 v 7.8.0
- Fix access control and protection against XSS for note attachments and other uploads. - Fix access control and protection against XSS for note attachments and other uploads.
...@@ -37,7 +56,7 @@ v 7.8.0 ...@@ -37,7 +56,7 @@ v 7.8.0
- Allow configuring protection of the default branch upon first push (Marco Wessel) - Allow configuring protection of the default branch upon first push (Marco Wessel)
- Add gitlab.com importer - Add gitlab.com importer
- Add an ability to login with gitlab.com - Add an ability to login with gitlab.com
- Add a commit calendar to the user profile (Hannes Rosenögger) - Add a commit calendar to the user profile (Hannes Rosenögger)
- Submit comment on command-enter - Submit comment on command-enter
- Notify all members of a group when that group is mentioned in a comment, for example: `@gitlab-org` or `@sales`. - Notify all members of a group when that group is mentioned in a comment, for example: `@gitlab-org` or `@sales`.
- Extend issue clossing pattern to include "Resolve", "Resolves", "Resolved", "Resolving" and "Close" - Extend issue clossing pattern to include "Resolve", "Resolves", "Resolved", "Resolving" and "Close"
...@@ -52,7 +71,7 @@ v 7.8.0 ...@@ -52,7 +71,7 @@ v 7.8.0
- API: Access groups with their path (Julien Bianchi) - API: Access groups with their path (Julien Bianchi)
- Added link to milestone and keeping resource context on smaller viewports for issues and merge requests (Jason Blanchard) - Added link to milestone and keeping resource context on smaller viewports for issues and merge requests (Jason Blanchard)
- Allow notification email to be set separately from primary email. - Allow notification email to be set separately from primary email.
- API: Add support for editing an existing project (Mika Mäenpää and Hannes Rosenögger) - API: Add support for editing an existing project (Mika Mäenpää and Hannes Rosenögger)
- Don't have Markdown preview fail for long comments/wiki pages. - Don't have Markdown preview fail for long comments/wiki pages.
- When test web hook - show error message instead of 500 error page if connection to hook url was reset - When test web hook - show error message instead of 500 error page if connection to hook url was reset
- Added support for firing system hooks on group create/destroy and adding/removing users to group (Boyan Tabakov) - Added support for firing system hooks on group create/destroy and adding/removing users to group (Boyan Tabakov)
...@@ -72,6 +91,7 @@ v 7.8.0 ...@@ -72,6 +91,7 @@ v 7.8.0
- Improve database performance for GitLab - Improve database performance for GitLab
- Add Asana service (Jeremy Benoist) - Add Asana service (Jeremy Benoist)
- Improve project web hooks with extra data - Improve project web hooks with extra data
- Slack username and channel options
v 7.7.2 v 7.7.2
- Update GitLab Shell to version 2.4.2 that fixes a bug when developers can push to protected branch - Update GitLab Shell to version 2.4.2 that fixes a bug when developers can push to protected branch
......
...@@ -30,6 +30,7 @@ gem 'omniauth-github' ...@@ -30,6 +30,7 @@ gem 'omniauth-github'
gem 'omniauth-shibboleth' gem 'omniauth-shibboleth'
gem 'omniauth-kerberos' gem 'omniauth-kerberos'
gem 'omniauth-gitlab' gem 'omniauth-gitlab'
gem 'omniauth-bitbucket'
gem 'doorkeeper', '2.1.0' gem 'doorkeeper', '2.1.0'
gem "rack-oauth2", "~> 1.0.5" gem "rack-oauth2", "~> 1.0.5"
...@@ -41,7 +42,7 @@ gem "browser" ...@@ -41,7 +42,7 @@ gem "browser"
gem "gitlab_git", '7.0.0.rc14' gem "gitlab_git", '7.0.0.rc14'
# Ruby/Rack Git Smart-HTTP Server Handler # Ruby/Rack Git Smart-HTTP Server Handler
gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack' gem 'gitlab-grack', '~> 2.0.0.rc2', require: 'grack'
# LDAP Auth # LDAP Auth
gem 'gitlab_omniauth-ldap', '1.2.0', require: "omniauth-ldap" gem 'gitlab_omniauth-ldap', '1.2.0', require: "omniauth-ldap"
...@@ -51,7 +52,7 @@ gem 'net-ldap' ...@@ -51,7 +52,7 @@ gem 'net-ldap'
gem 'gollum-lib', '~> 4.0.0' gem 'gollum-lib', '~> 4.0.0'
# Language detection # Language detection
gem "gitlab-linguist", "~> 3.0.0", require: "linguist" gem "gitlab-linguist", "~> 3.0.1", require: "linguist"
# API # API
gem "grape", "~> 0.6.1" gem "grape", "~> 0.6.1"
...@@ -204,6 +205,7 @@ group :development do ...@@ -204,6 +205,7 @@ group :development do
gem "letter_opener" gem "letter_opener"
gem 'quiet_assets', '~> 1.0.1' gem 'quiet_assets', '~> 1.0.1'
gem 'rack-mini-profiler', require: false gem 'rack-mini-profiler', require: false
gem "byebug"
# Better errors handler # Better errors handler
gem 'better_errors' gem 'better_errors'
......
...@@ -65,6 +65,9 @@ GEM ...@@ -65,6 +65,9 @@ GEM
sass (>= 3.2.19) sass (>= 3.2.19)
browser (0.7.2) browser (0.7.2)
builder (3.2.2) builder (3.2.2)
byebug (3.2.0)
columnize (~> 0.8)
debugger-linecache (~> 1.2)
cal-heatmap-rails (0.0.1) cal-heatmap-rails (0.0.1)
capybara (2.2.1) capybara (2.2.1)
mime-types (>= 1.16) mime-types (>= 1.16)
...@@ -78,7 +81,7 @@ GEM ...@@ -78,7 +81,7 @@ GEM
json (>= 1.7) json (>= 1.7)
celluloid (0.16.0) celluloid (0.16.0)
timers (~> 4.0.0) timers (~> 4.0.0)
charlock_holmes (0.7.3) charlock_holmes (0.6.9.4)
cliver (0.3.2) cliver (0.3.2)
coderay (1.1.0) coderay (1.1.0)
coercible (1.0.0) coercible (1.0.0)
...@@ -92,6 +95,7 @@ GEM ...@@ -92,6 +95,7 @@ GEM
coffee-script-source (1.6.3) coffee-script-source (1.6.3)
colored (1.2) colored (1.2)
colorize (0.5.8) colorize (0.5.8)
columnize (0.9.0)
connection_pool (2.1.0) connection_pool (2.1.0)
coveralls (0.7.0) coveralls (0.7.0)
multi_json (~> 1.3) multi_json (~> 1.3)
...@@ -107,6 +111,7 @@ GEM ...@@ -107,6 +111,7 @@ GEM
daemons (1.1.9) daemons (1.1.9)
database_cleaner (1.3.0) database_cleaner (1.3.0)
debug_inspector (0.0.2) debug_inspector (0.0.2)
debugger-linecache (1.2.0)
default_value_for (3.0.0) default_value_for (3.0.0)
activerecord (>= 3.2.0, < 5.0) activerecord (>= 3.2.0, < 5.0)
descendants_tracker (0.0.3) descendants_tracker (0.0.3)
...@@ -174,8 +179,8 @@ GEM ...@@ -174,8 +179,8 @@ GEM
dotenv (>= 0.7) dotenv (>= 0.7)
thor (>= 0.13.6) thor (>= 0.13.6)
formatador (0.2.4) formatador (0.2.4)
gemnasium-gitlab-service (0.2.3) gemnasium-gitlab-service (0.2.4)
rugged (~> 0.19) rugged (~> 0.21)
gherkin-ruby (0.3.1) gherkin-ruby (0.3.1)
racc racc
github-markup (1.3.1) github-markup (1.3.1)
...@@ -183,14 +188,14 @@ GEM ...@@ -183,14 +188,14 @@ GEM
gitlab-flowdock-git-hook (0.4.2.2) gitlab-flowdock-git-hook (0.4.2.2)
gitlab-grit (>= 2.4.1) gitlab-grit (>= 2.4.1)
multi_json multi_json
gitlab-grack (2.0.0.pre) gitlab-grack (2.0.0.rc2)
rack (~> 1.5.1) rack (~> 1.5.1)
gitlab-grit (2.7.2) gitlab-grit (2.7.2)
charlock_holmes (~> 0.6) charlock_holmes (~> 0.6)
diff-lcs (~> 1.1) diff-lcs (~> 1.1)
mime-types (~> 1.15) mime-types (~> 1.15)
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab-linguist (3.0.0) gitlab-linguist (3.0.1)
charlock_holmes (~> 0.6.6) charlock_holmes (~> 0.6.6)
escape_utils (~> 0.2.4) escape_utils (~> 0.2.4)
mime-types (~> 1.19) mime-types (~> 1.19)
...@@ -339,6 +344,10 @@ GEM ...@@ -339,6 +344,10 @@ GEM
omniauth (1.1.4) omniauth (1.1.4)
hashie (>= 1.2, < 3) hashie (>= 1.2, < 3)
rack rack
omniauth-bitbucket (0.0.2)
multi_json (~> 1.7)
omniauth (~> 1.1)
omniauth-oauth (~> 1.0)
omniauth-github (1.1.1) omniauth-github (1.1.1)
omniauth (~> 1.0) omniauth (~> 1.0)
omniauth-oauth2 (~> 1.1) omniauth-oauth2 (~> 1.1)
...@@ -490,7 +499,7 @@ GEM ...@@ -490,7 +499,7 @@ GEM
ruby-progressbar (1.7.1) ruby-progressbar (1.7.1)
rubyntlm (0.4.0) rubyntlm (0.4.0)
rubypants (0.2.0) rubypants (0.2.0)
rugged (0.21.2) rugged (0.21.4)
rugments (1.0.0.beta3) rugments (1.0.0.beta3)
safe_yaml (0.9.7) safe_yaml (0.9.7)
sanitize (2.1.0) sanitize (2.1.0)
...@@ -613,7 +622,7 @@ GEM ...@@ -613,7 +622,7 @@ GEM
raindrops (~> 0.7) raindrops (~> 0.7)
unicorn-worker-killer (0.4.2) unicorn-worker-killer (0.4.2)
unicorn (~> 4) unicorn (~> 4)
version_sorter (1.1.0) version_sorter (2.0.0)
virtus (1.0.1) virtus (1.0.1)
axiom-types (~> 0.0.5) axiom-types (~> 0.0.5)
coercible (~> 1.0) coercible (~> 1.0)
...@@ -648,6 +657,7 @@ DEPENDENCIES ...@@ -648,6 +657,7 @@ DEPENDENCIES
binding_of_caller binding_of_caller
bootstrap-sass (~> 3.0) bootstrap-sass (~> 3.0)
browser browser
byebug
cal-heatmap-rails (~> 0.0.1) cal-heatmap-rails (~> 0.0.1)
capybara (~> 2.2.1) capybara (~> 2.2.1)
carrierwave carrierwave
...@@ -673,8 +683,8 @@ DEPENDENCIES ...@@ -673,8 +683,8 @@ DEPENDENCIES
gemnasium-gitlab-service (~> 0.2) gemnasium-gitlab-service (~> 0.2)
github-markup github-markup
gitlab-flowdock-git-hook (~> 0.4.2) gitlab-flowdock-git-hook (~> 0.4.2)
gitlab-grack (~> 2.0.0.pre) gitlab-grack (~> 2.0.0.rc2)
gitlab-linguist (~> 3.0.0) gitlab-linguist (~> 3.0.1)
gitlab_emoji (~> 0.0.1.1) gitlab_emoji (~> 0.0.1.1)
gitlab_git (= 7.0.0.rc14) gitlab_git (= 7.0.0.rc14)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
...@@ -707,6 +717,7 @@ DEPENDENCIES ...@@ -707,6 +717,7 @@ DEPENDENCIES
nprogress-rails nprogress-rails
octokit (= 3.7.0) octokit (= 3.7.0)
omniauth (~> 1.1.3) omniauth (~> 1.1.3)
omniauth-bitbucket
omniauth-github omniauth-github
omniauth-gitlab omniauth-gitlab
omniauth-google-oauth2 omniauth-google-oauth2
......
app/assets/images/logo-white.png

7.16 KB | W: | H:

app/assets/images/logo-white.png

7.52 KB | W: | H:

app/assets/images/logo-white.png
app/assets/images/logo-white.png
app/assets/images/logo-white.png
app/assets/images/logo-white.png
  • 2-up
  • Swipe
  • Onion skin
class @Diff class @Diff
UNFOLD_COUNT = 20 UNFOLD_COUNT = 20
constructor: -> constructor: ->
$(document).off('click', '.js-unfold')
$(document).on('click', '.js-unfold', (event) => $(document).on('click', '.js-unfold', (event) =>
target = $(event.target) target = $(event.target)
unfoldBottom = target.hasClass('js-unfold-bottom') unfoldBottom = target.hasClass('js-unfold-bottom')
...@@ -36,7 +37,7 @@ class @Diff ...@@ -36,7 +37,7 @@ class @Diff
) )
) )
$('.diff-header').stick_in_parent(offset_top: $('.navbar').height()) $('.diff-header').stick_in_parent(recalc_every: 1, offset_top: $('.navbar').height())
lineNumbers: (line) -> lineNumbers: (line) ->
return ([0, 0]) unless line.children().length return ([0, 0]) unless line.children().length
......
...@@ -6,10 +6,10 @@ class @DropzoneInput ...@@ -6,10 +6,10 @@ class @DropzoneInput
divHover = "<div class=\"div-dropzone-hover\"></div>" divHover = "<div class=\"div-dropzone-hover\"></div>"
divSpinner = "<div class=\"div-dropzone-spinner\"></div>" divSpinner = "<div class=\"div-dropzone-spinner\"></div>"
divAlert = "<div class=\"" + alertClass + "\"></div>" divAlert = "<div class=\"" + alertClass + "\"></div>"
iconPicture = "<i class=\"fa fa-picture-o div-dropzone-icon\"></i>" iconPaperclip = "<i class=\"fa fa-paperclip div-dropzone-icon\"></i>"
iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>" iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>"
btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>" btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>"
project_image_path_upload = window.project_image_path_upload or null project_uploads_path = window.project_uploads_path or null
form_textarea = $(form).find("textarea.markdown-area") form_textarea = $(form).find("textarea.markdown-area")
form_textarea.wrap "<div class=\"div-dropzone\"></div>" form_textarea.wrap "<div class=\"div-dropzone\"></div>"
...@@ -19,7 +19,7 @@ class @DropzoneInput ...@@ -19,7 +19,7 @@ class @DropzoneInput
form_dropzone = $(form).find('.div-dropzone') form_dropzone = $(form).find('.div-dropzone')
form_dropzone.parent().addClass "div-dropzone-wrapper" form_dropzone.parent().addClass "div-dropzone-wrapper"
form_dropzone.append divHover form_dropzone.append divHover
$(".div-dropzone-hover").append iconPicture $(".div-dropzone-hover").append iconPaperclip
form_dropzone.append divSpinner form_dropzone.append divSpinner
$(".div-dropzone-spinner").append iconSpinner $(".div-dropzone-spinner").append iconSpinner
$(".div-dropzone-spinner").css $(".div-dropzone-spinner").css
...@@ -72,13 +72,12 @@ class @DropzoneInput ...@@ -72,13 +72,12 @@ class @DropzoneInput
form.find(".md-preview-holder").hide() form.find(".md-preview-holder").hide()
dropzone = form_dropzone.dropzone( dropzone = form_dropzone.dropzone(
url: project_image_path_upload url: project_uploads_path
dictDefaultMessage: "" dictDefaultMessage: ""
clickable: true clickable: true
paramName: "markdown_img" paramName: "file"
maxFilesize: 10 maxFilesize: 10
uploadMultiple: false uploadMultiple: false
acceptedFiles: "image/jpg,image/jpeg,image/gif,image/png"
headers: headers:
"X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content") "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
...@@ -132,8 +131,10 @@ class @DropzoneInput ...@@ -132,8 +131,10 @@ class @DropzoneInput
child = $(dropzone[0]).children("textarea") child = $(dropzone[0]).children("textarea")
formatLink = (str) -> formatLink = (link) ->
"![" + str.alt + "](" + str.url + ")" text = "[#{link.alt}](#{link.url})"
text = "!#{text}" if link.is_image
text
handlePaste = (event) -> handlePaste = (event) ->
pasteEvent = event.originalEvent pasteEvent = event.originalEvent
...@@ -177,9 +178,9 @@ class @DropzoneInput ...@@ -177,9 +178,9 @@ class @DropzoneInput
uploadFile = (item, filename) -> uploadFile = (item, filename) ->
formData = new FormData() formData = new FormData()
formData.append "markdown_img", item, filename formData.append "file", item, filename
$.ajax $.ajax
url: project_image_path_upload url: project_uploads_path
type: "POST" type: "POST"
data: formData data: formData
dataType: "json" dataType: "json"
...@@ -233,5 +234,7 @@ class @DropzoneInput ...@@ -233,5 +234,7 @@ class @DropzoneInput
$(@).closest('.gfm-form').find('.div-dropzone').click() $(@).closest('.gfm-form').find('.div-dropzone').click()
return return
formatLink: (str) -> formatLink: (link) ->
"![" + str.alt + "](" + str.url + ")" text = "[#{link.alt}](#{link.url})"
text = "!#{text}" if link.is_image
text
\ No newline at end of file
...@@ -16,8 +16,9 @@ class @Issue ...@@ -16,8 +16,9 @@ class @Issue
updateTaskState updateTaskState
) )
$('.issuable-affix').affix offset: $('.issue-details').waitForImages ->
top: -> $('.issuable-affix').affix offset:
@top = $('.issue-details').outerHeight(true) + 25 top: ->
bottom: -> @top = $('.issue-details').outerHeight(true) + 25
@bottom = $('.footer').outerHeight(true) bottom: ->
@bottom = $('.footer').outerHeight(true)
...@@ -20,11 +20,12 @@ class @MergeRequest ...@@ -20,11 +20,12 @@ class @MergeRequest
if $("a.btn-close").length if $("a.btn-close").length
$("li.task-list-item input:checkbox").prop("disabled", false) $("li.task-list-item input:checkbox").prop("disabled", false)
$('.issuable-affix').affix offset: $('.merge-request-details').waitForImages ->
top: -> $('.issuable-affix').affix offset:
@top = $('.merge-request-details').outerHeight(true) + 70 top: ->
bottom: -> @top = $('.merge-request-details').outerHeight(true) + 91
@bottom = $('.footer').outerHeight(true) bottom: ->
@bottom = $('.footer').outerHeight(true)
# Local jQuery finder # Local jQuery finder
$: (selector) -> $: (selector) ->
...@@ -95,6 +96,7 @@ class @MergeRequest ...@@ -95,6 +96,7 @@ class @MergeRequest
this.$('.merge-request-tabs .diffs-tab').addClass 'active' this.$('.merge-request-tabs .diffs-tab').addClass 'active'
this.loadDiff() unless @diffs_loaded this.loadDiff() unless @diffs_loaded
this.$('.diffs').show() this.$('.diffs').show()
$(".diff-header").trigger("sticky_kit:recalc")
when 'commits' when 'commits'
this.$('.merge-request-tabs .commits-tab').addClass 'active' this.$('.merge-request-tabs .commits-tab').addClass 'active'
this.$('.commits').show() this.$('.commits').show()
...@@ -123,7 +125,7 @@ class @MergeRequest ...@@ -123,7 +125,7 @@ class @MergeRequest
loadDiff: (event) -> loadDiff: (event) ->
$.ajax $.ajax
type: 'GET' type: 'GET'
url: this.$('.merge-request-tabs .diffs-tab a').attr('href') url: this.$('.merge-request-tabs .diffs-tab a').attr('href') + ".json"
beforeSend: => beforeSend: =>
this.$('.mr-loading-status .loading').show() this.$('.mr-loading-status .loading').show()
complete: => complete: =>
......
...@@ -39,9 +39,6 @@ class @Notes ...@@ -39,9 +39,6 @@ class @Notes
# reset main target form after submit # reset main target form after submit
$(document).on "ajax:complete", ".js-main-target-form", @resetMainTargetForm $(document).on "ajax:complete", ".js-main-target-form", @resetMainTargetForm
# attachment button
$(document).on "click", ".js-choose-note-attachment-button", @chooseNoteAttachment
# update the file name when an attachment is selected # update the file name when an attachment is selected
$(document).on "change", ".js-note-attachment-input", @updateFormAttachment $(document).on "change", ".js-note-attachment-input", @updateFormAttachment
...@@ -73,7 +70,6 @@ class @Notes ...@@ -73,7 +70,6 @@ class @Notes
$(document).off "click", ".js-note-delete" $(document).off "click", ".js-note-delete"
$(document).off "click", ".js-note-attachment-delete" $(document).off "click", ".js-note-attachment-delete"
$(document).off "ajax:complete", ".js-main-target-form" $(document).off "ajax:complete", ".js-main-target-form"
$(document).off "click", ".js-choose-note-attachment-button"
$(document).off "click", ".js-discussion-reply-button" $(document).off "click", ".js-discussion-reply-button"
$(document).off "click", ".js-add-diff-note-button" $(document).off "click", ".js-add-diff-note-button"
$(document).off "visibilitychange" $(document).off "visibilitychange"
...@@ -173,15 +169,6 @@ class @Notes ...@@ -173,15 +169,6 @@ class @Notes
form.find(".js-note-text").data("autosave").reset() form.find(".js-note-text").data("autosave").reset()
###
Called when clicking the "Choose File" button.
Opens the file selection dialog.
###
chooseNoteAttachment: ->
form = $(this).closest("form")
form.find(".js-note-attachment-input").click()
### ###
Shows the main form and does some setup on it. Shows the main form and does some setup on it.
......
...@@ -15,7 +15,7 @@ class @ProjectUsersSelect ...@@ -15,7 +15,7 @@ class @ProjectUsersSelect
name: 'Unassigned', name: 'Unassigned',
avatar: null, avatar: null,
username: 'none', username: 'none',
id: '' id: -1
} }
data.results.unshift(nullUser) data.results.unshift(nullUser)
......
...@@ -69,5 +69,6 @@ ...@@ -69,5 +69,6 @@
background: #EEE; background: #EEE;
font-size: 20px; font-size: 20px;
color: #777; color: #777;
z-index: 100;
@include box-shadow(0 1px 2px #DDD); @include box-shadow(0 1px 2px #DDD);
} }
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
border-bottom: 1px solid #CCC; border-bottom: 1px solid #CCC;
padding: 5px 5px 5px 10px; padding: 5px 5px 5px 10px;
color: #555; color: #555;
z-index: 10;
> span { > span {
font-family: $monospace_font; font-family: $monospace_font;
......
...@@ -184,6 +184,12 @@ ...@@ -184,6 +184,12 @@
} }
} }
.event_filter li a { .event_filter {
padding: 5px 10px;
li a {
padding: 5px 10px;
background: rgba(0,0,0,0.045);
margin-left: 4px;
}
} }
...@@ -86,7 +86,7 @@ header { ...@@ -86,7 +86,7 @@ header {
.container { .container {
width: 100% !important; width: 100% !important;
padding-left: 0px; padding: 0px;
} }
/** /**
...@@ -100,18 +100,14 @@ header { ...@@ -100,18 +100,14 @@ header {
a { a {
float: left; float: left;
padding: 0px; padding: 5px 0;
margin: 0 6px; height: 46px;
width: 52px;
h1 { text-align: center;
margin: 0;
background: image-url('logo-black.png') no-repeat center center; img {
background-size: 32px; width: 36px;
float: left; height: 36px;
height: 46px;
width: 40px;
@include header-font;
text-indent: -9999px;
} }
} }
&:hover { &:hover {
...@@ -134,14 +130,13 @@ header { ...@@ -134,14 +130,13 @@ header {
} }
.profile-pic { .profile-pic {
position: relative; padding: 0px !important;
top: -1px; width: 46px;
padding-right: 0px !important; height: 46px;
margin-left: 5px;
img { img {
width: 50px; width: 46px;
height: 50px; height: 46px;
margin: -15px;
margin-left: 5px;
} }
} }
...@@ -174,68 +169,6 @@ header { ...@@ -174,68 +169,6 @@ header {
@include transition(all 0.15s ease-in 0s); @include transition(all 0.15s ease-in 0s);
} }
} }
/*
* Dark header
*
*/
&.header-dark {
&.navbar-gitlab {
.navbar-inner {
background: #708090;
border-bottom: 1px solid #AAA;
.navbar-toggle { color: #fff; }
.nav > li > a {
color: #AAA;
&:hover, &:focus, &:active {
background: none;
color: #FFF;
}
}
}
}
.turbolink-spinner {
color: #FFF;
}
.search {
.search-input {
background-color: #D2D5DA;
background-color: rgba(255, 255, 255, 0.5);
border: 1px solid #AAA;
&:focus {
background-color: white;
}
}
}
.search-input::-webkit-input-placeholder {
color: #666;
}
.app_logo {
a {
h1 {
background: image-url('logo-white.png') no-repeat center center;
background-size: 32px;
color: #fff;
}
}
}
.title {
a {
color: #FFF;
&:hover {
text-decoration: underline;
}
}
color: #fff;
}
}
} }
.search .search-input { .search .search-input {
......
i.icon-gitorious {
display: inline-block;
background-position: 0px 0px;
background-size: contain;
background-repeat: no-repeat;
}
i.icon-gitorious-small {
background-image: image-url('gitorious-logo-blue.png');
width: 13px;
height: 13px;
}
i.icon-gitorious-big {
background-image: image-url('gitorious-logo-black.png');
width: 18px;
height: 18px;
}
...@@ -108,7 +108,7 @@ ...@@ -108,7 +108,7 @@
width: $sidebar_width; width: $sidebar_width;
.nav-sidebar { .nav-sidebar {
margin-top: 20px; margin-top: 29px;
position: fixed; position: fixed;
top: 45px; top: 45px;
width: $sidebar_width; width: $sidebar_width;
...@@ -127,7 +127,7 @@ ...@@ -127,7 +127,7 @@
width: 52px; width: 52px;
.nav-sidebar { .nav-sidebar {
margin-top: 20px; margin-top: 29px;
position: fixed; position: fixed;
top: 45px; top: 45px;
width: 52px; width: 52px;
...@@ -144,14 +144,22 @@ ...@@ -144,14 +144,22 @@
} }
} }
} }
.collapse-nav a {
left: 0px;
padding: 5px 23px 3px 22px;
}
} }
} }
.collapse-nav a { .collapse-nav a {
position: fixed; position: fixed;
bottom: 15px; top: 47px;
padding: 10px; padding: 5px 13px 3px 13px;
background: #DDD; left: 197px;
background: #EEE;
color: black;
border: 1px solid rgba(0,0,0,0.035);
} }
@media (max-width: $screen-md-max) { @media (max-width: $screen-md-max) {
......
...@@ -66,6 +66,22 @@ ul.notes { ...@@ -66,6 +66,22 @@ ul.notes {
overflow: auto; overflow: auto;
word-wrap: break-word; word-wrap: break-word;
@include md-typography; @include md-typography;
a[href*="/uploads/"] {
&:before {
margin-right: 4px;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
content: "\f0c6";
}
&:hover:before {
text-decoration: none;
}
}
} }
} }
.note-header { .note-header {
......
@mixin dark-theme($color-light, $color, $color-darker, $color-dark) {
header {
&.navbar-gitlab {
.navbar-inner {
background: $color;
.navbar-toggle {
color: #FFF;
}
.app_logo, .navbar-toggle {
&:hover {
background-color: $color-darker;
}
}
.app_logo {
background-color: $color-dark;
}
.title {
color: #FFF;
a {
color: #FFF;
&:hover {
text-decoration: underline;
}
}
}
.search {
.search-input {
background-color: $color-light;
background-color: rgba(255, 255, 255, 0.5);
border: 1px solid $color-light;
&:focus {
background-color: white;
}
}
}
.search-input::-webkit-input-placeholder {
color: #666;
}
.nav > li > a {
color: $color-light;
&:hover, &:focus, &:active {
background: none;
color: #FFF;
}
}
.search-input {
border-color: $color-light;
}
}
}
}
}
...@@ -10,8 +10,15 @@ ...@@ -10,8 +10,15 @@
background: #F1F1F1; background: #F1F1F1;
border-bottom: 1px solid #DDD; border-bottom: 1px solid #DDD;
.app_logo { .title {
background-color: #DDD; color: #555;
a {
color: #555;
&:hover {
text-decoration: underline;
}
}
} }
.nav > li > a { .nav > li > a {
......
/** /**
* This file represent some UI that can be changed * Violet GitLab UI theme
* during web app restyle or theme select.
*
* Next items should be placed there
* - link colors
* - header restyles
*
*/ */
.ui_color { .ui_color {
/* @include dark-theme(#98C, #548, #436, #325);
* Application Header
*
*/
header {
@extend .header-dark;
&.navbar-gitlab {
.navbar-inner {
background: #548;
border-bottom: 1px solid #436;
.app_logo, .navbar-toggle {
&:hover {
background-color: #436;
}
}
.app_logo {
background-color: #325;
}
.nav > li > a {
color: #98C;
}
.search-input {
border-color: #98C;
}
}
}
}
.nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus {
background: #659;
}
} }
/** /**
* This file represent some UI that can be changed * Gray GitLab UI theme
* during web app restyle or theme select.
*
* Next items should be placed there
* - link colors
* - header restyles
*
*/ */
.ui_gray { .ui_gray {
/* @include dark-theme(#979797, #373737, #272727, #222222);
* Application Header
*
*/
header {
@extend .header-dark;
&.navbar-gitlab {
.navbar-inner {
background: #373737;
border-bottom: 1px solid #272727;
.app_logo, .navbar-toggle {
&:hover {
background-color: #272727;
}
}
.app_logo {
background-color: #222;
}
}
}
}
} }
/** /**
* This file represent some UI that can be changed * Classic GitLab UI theme
* during web app restyle or theme select.
*
* Next items should be placed there
* - link colors
* - header restyles
*
*/ */
.ui_mars { .ui_mars {
/* @include dark-theme(#979DA7, #474D57, #373D47, #24272D);
* Application Header
*
*/
header {
@extend .header-dark;
&.navbar-gitlab {
.navbar-inner {
background: #474D57;
border-bottom: 1px solid #373D47;
.app_logo, .navbar-toggle {
&:hover {
background-color: #373D47;
}
}
.app_logo {
background-color: #24272D;
}
.nav > li > a {
color: #979DA7;
}
.search-input {
border-color: #979DA7;
}
}
}
}
} }
/** /**
* This file represent some UI that can be changed * Modern GitLab UI theme
* during web app restyle or theme select.
*
* Next items should be placed there
* - link colors
* - header restyles
*
*/ */
.ui_modern { .ui_modern {
/* @include dark-theme(#ADC, #019875, #018865, #017855);
* Application Header
*
*/
header {
@extend .header-dark;
&.navbar-gitlab {
.navbar-inner {
background: #019875;
border-bottom: 1px solid #019875;
.app_logo, .navbar-toggle {
&:hover {
background-color: #018865;
}
}
.app_logo {
background-color: #017855;
}
.nav > li > a {
color: #ADC;
}
.search-input {
border-color: #8ba;
}
}
}
}
.nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus {
background: #019875;
}
} }
...@@ -2,6 +2,7 @@ require 'gon' ...@@ -2,6 +2,7 @@ require 'gon'
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
include Gitlab::CurrentSettings include Gitlab::CurrentSettings
include GitlabRoutingHelper
before_filter :authenticate_user_from_token! before_filter :authenticate_user_from_token!
before_filter :authenticate_user! before_filter :authenticate_user!
...@@ -16,6 +17,7 @@ class ApplicationController < ActionController::Base ...@@ -16,6 +17,7 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception protect_from_forgery with: :exception
helper_method :abilities, :can?, :current_application_settings helper_method :abilities, :can?, :current_application_settings
helper_method :github_import_enabled?, :gitlab_import_enabled?, :bitbucket_import_enabled?
rescue_from Encoding::CompatibilityError do |exception| rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception) log_exception(exception)
...@@ -319,4 +321,16 @@ class ApplicationController < ActionController::Base ...@@ -319,4 +321,16 @@ class ApplicationController < ActionController::Base
set_filter_values(merge_requests) set_filter_values(merge_requests)
merge_requests merge_requests
end end
def github_import_enabled?
OauthHelper.enabled_oauth_providers.include?(:github)
end
def gitlab_import_enabled?
OauthHelper.enabled_oauth_providers.include?(:gitlab)
end
def bitbucket_import_enabled?
OauthHelper.enabled_oauth_providers.include?(:bitbucket) && Gitlab::BitbucketImport.public_key.present?
end
end end
...@@ -47,7 +47,7 @@ class DashboardController < ApplicationController ...@@ -47,7 +47,7 @@ class DashboardController < ApplicationController
@projects = @projects.where(namespace_id: Group.find_by(name: params[:group])) if params[:group].present? @projects = @projects.where(namespace_id: Group.find_by(name: params[:group])) if params[:group].present?
@projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present? @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
@projects = @projects.includes(:namespace) @projects = @projects.includes(:namespace, :forked_from_project, :tags)
@projects = @projects.tagged_with(params[:tag]) if params[:tag].present? @projects = @projects.tagged_with(params[:tag]) if params[:tag].present?
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]).per(30) @projects = @projects.page(params[:page]).per(30)
......
class FilesController < ApplicationController
def download
note = Note.find(params[:id])
uploader = note.attachment
if uploader.file_storage?
if can?(current_user, :read_project, note.project)
disposition = uploader.image? ? 'inline' : 'attachment'
send_file uploader.file.path, disposition: disposition
else
not_found!
end
else
redirect_to uploader.url
end
end
end
class Import::BitbucketController < Import::BaseController
before_filter :verify_bitbucket_import_enabled
before_filter :bitbucket_auth, except: :callback
rescue_from OAuth::Error, with: :bitbucket_unauthorized
def callback
request_token = session.delete(:oauth_request_token)
raise "Session expired!" if request_token.nil?
request_token.symbolize_keys!
access_token = client.get_token(request_token, params[:oauth_verifier], callback_import_bitbucket_url)
current_user.bitbucket_access_token = access_token.token
current_user.bitbucket_access_token_secret = access_token.secret
current_user.save
redirect_to status_import_bitbucket_url
end
def status
@repos = client.projects
@already_added_projects = current_user.created_projects.where(import_type: "bitbucket")
already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos.to_a.reject!{ |repo| already_added_projects_names.include? "#{repo["owner"]}/#{repo["slug"]}" }
end
def jobs
jobs = current_user.created_projects.where(import_type: "bitbucket").to_json(only: [:id, :import_status])
render json: jobs
end
def create
@repo_id = params[:repo_id] || ""
repo = client.project(@repo_id.gsub("___", "/"))
@target_namespace = params[:new_namespace].presence || repo["owner"]
@project_name = repo["slug"]
namespace = get_or_create_namespace || (render and return)
unless Gitlab::BitbucketImport::KeyAdder.new(repo, current_user).execute
@access_denied = true
render
return
end
@project = Gitlab::BitbucketImport::ProjectCreator.new(repo, namespace, current_user).execute
end
private
def client
@client ||= Gitlab::BitbucketImport::Client.new(current_user.bitbucket_access_token, current_user.bitbucket_access_token_secret)
end
def verify_bitbucket_import_enabled
not_found! unless bitbucket_import_enabled?
end
def bitbucket_auth
if current_user.bitbucket_access_token.blank?
go_to_bitbucket_for_permissions
end
end
def go_to_bitbucket_for_permissions
request_token = client.request_token(callback_import_bitbucket_url)
session[:oauth_request_token] = request_token
redirect_to client.authorize_url(request_token, callback_import_bitbucket_url)
end
def bitbucket_unauthorized
go_to_bitbucket_for_permissions
end
end
class Import::GithubController < Import::BaseController class Import::GithubController < Import::BaseController
before_filter :verify_github_import_enabled
before_filter :github_auth, except: :callback before_filter :github_auth, except: :callback
rescue_from Octokit::Unauthorized, with: :github_unauthorized rescue_from Octokit::Unauthorized, with: :github_unauthorized
...@@ -44,6 +45,10 @@ class Import::GithubController < Import::BaseController ...@@ -44,6 +45,10 @@ class Import::GithubController < Import::BaseController
@client ||= Gitlab::GithubImport::Client.new(current_user.github_access_token) @client ||= Gitlab::GithubImport::Client.new(current_user.github_access_token)
end end
def verify_github_import_enabled
not_found! unless github_import_enabled?
end
def github_auth def github_auth
if current_user.github_access_token.blank? if current_user.github_access_token.blank?
go_to_github_for_permissions go_to_github_for_permissions
......
class Import::GitlabController < Import::BaseController class Import::GitlabController < Import::BaseController
before_filter :verify_gitlab_import_enabled
before_filter :gitlab_auth, except: :callback before_filter :gitlab_auth, except: :callback
rescue_from OAuth2::Error, with: :gitlab_unauthorized rescue_from OAuth2::Error, with: :gitlab_unauthorized
...@@ -16,7 +17,7 @@ class Import::GitlabController < Import::BaseController ...@@ -16,7 +17,7 @@ class Import::GitlabController < Import::BaseController
@already_added_projects = current_user.created_projects.where(import_type: "gitlab") @already_added_projects = current_user.created_projects.where(import_type: "gitlab")
already_added_projects_names = @already_added_projects.pluck(:import_source) already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos.to_a.reject!{ |repo| already_added_projects_names.include? repo["path_with_namespace"] } @repos = @repos.to_a.reject{ |repo| already_added_projects_names.include? repo["path_with_namespace"] }
end end
def jobs def jobs
...@@ -41,6 +42,10 @@ class Import::GitlabController < Import::BaseController ...@@ -41,6 +42,10 @@ class Import::GitlabController < Import::BaseController
@client ||= Gitlab::GitlabImport::Client.new(current_user.gitlab_access_token) @client ||= Gitlab::GitlabImport::Client.new(current_user.gitlab_access_token)
end end
def verify_gitlab_import_enabled
not_found! unless gitlab_import_enabled?
end
def gitlab_auth def gitlab_auth
if current_user.gitlab_access_token.blank? if current_user.gitlab_access_token.blank?
go_to_gitlab_for_permissions go_to_gitlab_for_permissions
......
...@@ -15,7 +15,7 @@ class Import::GitoriousController < Import::BaseController ...@@ -15,7 +15,7 @@ class Import::GitoriousController < Import::BaseController
@already_added_projects = current_user.created_projects.where(import_type: "gitorious") @already_added_projects = current_user.created_projects.where(import_type: "gitorious")
already_added_projects_names = @already_added_projects.pluck(:import_source) already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos.to_a.reject! { |repo| already_added_projects_names.include? repo.full_name } @repos.reject! { |repo| already_added_projects_names.include? repo.full_name }
end end
def jobs def jobs
......
...@@ -24,6 +24,6 @@ class Projects::AvatarsController < Projects::ApplicationController ...@@ -24,6 +24,6 @@ class Projects::AvatarsController < Projects::ApplicationController
@project.save @project.save
@project.reset_events_cache @project.reset_events_cache
redirect_to edit_namespace_project_path(@project.namespace, @project) redirect_to edit_project_path(@project)
end end
end end
# Controller for viewing a file's blame # Controller for viewing a file's blame
class Projects::BlobController < Projects::ApplicationController class Projects::BlobController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include ActionView::Helpers::SanitizeHelper
# Raised when given an invalid file path # Raised when given an invalid file path
class InvalidPathError < StandardError; end class InvalidPathError < StandardError; end
...@@ -21,11 +22,18 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -21,11 +22,18 @@ class Projects::BlobController < Projects::ApplicationController
def create def create
file_path = File.join(@path, File.basename(params[:file_name])) file_path = File.join(@path, File.basename(params[:file_name]))
result = Files::CreateService.new(@project, current_user, params, @ref, file_path).execute result = Files::CreateService.new(
@project,
current_user,
params.merge(new_branch: sanitized_new_branch_name),
@ref,
file_path
).execute
if result[:status] == :success if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed" flash[:notice] = "Your changes have been successfully committed"
redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@ref, file_path)) ref = sanitized_new_branch_name.presence || @ref
redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(ref, file_path))
else else
flash[:alert] = result[:message] flash[:alert] = result[:message]
render :new render :new
...@@ -41,7 +49,13 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -41,7 +49,13 @@ class Projects::BlobController < Projects::ApplicationController
def update def update
result = Files::UpdateService. result = Files::UpdateService.
new(@project, current_user, params, @ref, @path).execute new(
@project,
current_user,
params.merge(new_branch: sanitized_new_branch_name),
@ref,
@path
).execute
if result[:status] == :success if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed" flash[:notice] = "Your changes have been successfully committed"
...@@ -131,6 +145,8 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -131,6 +145,8 @@ class Projects::BlobController < Projects::ApplicationController
if from_merge_request if from_merge_request
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
"#file-path-#{hexdigest(@path)}" "#file-path-#{hexdigest(@path)}"
elsif sanitized_new_branch_name.present?
namespace_project_blob_path(@project.namespace, @project, File.join(sanitized_new_branch_name, @path))
else else
namespace_project_blob_path(@project.namespace, @project, @id) namespace_project_blob_path(@project.namespace, @project, @id)
end end
...@@ -140,4 +156,8 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -140,4 +156,8 @@ class Projects::BlobController < Projects::ApplicationController
# If blob edit was initiated from merge request page # If blob edit was initiated from merge request page
@from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id]) @from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id])
end end
def sanitized_new_branch_name
@new_branch ||= sanitize(strip_tags(params[:new_branch]))
end
end end
...@@ -60,8 +60,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -60,8 +60,7 @@ class Projects::IssuesController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html do format.html do
if @issue.valid? if @issue.valid?
redirect_to namespace_project_issue_path(@project.namespace, redirect_to issue_path(@issue)
@project, @issue)
else else
render :new render :new
end end
...@@ -79,7 +78,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -79,7 +78,7 @@ class Projects::IssuesController < Projects::ApplicationController
format.js format.js
format.html do format.html do
if @issue.valid? if @issue.valid?
redirect_to [@project.namespace.becomes(Namespace), @project, @issue] redirect_to issue_path(@issue)
else else
render :edit render :edit
end end
...@@ -129,8 +128,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -129,8 +128,7 @@ class Projects::IssuesController < Projects::ApplicationController
issue = @project.issues.find_by(id: params[:id]) issue = @project.issues.find_by(id: params[:id])
if issue if issue
redirect_to namespace_project_issue_path(@project.namespace, @project, redirect_to issue_path(issue)
issue)
return return
else else
raise ActiveRecord::RecordNotFound.new raise ActiveRecord::RecordNotFound.new
......
...@@ -79,9 +79,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -79,9 +79,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
if @merge_request.valid? if @merge_request.valid?
redirect_to( redirect_to(
namespace_project_merge_request_path(@merge_request.target_project.namespace, merge_request_path(@merge_request),
@merge_request.target_project,
@merge_request),
notice: 'Merge request was successfully created.' notice: 'Merge request was successfully created.'
) )
else else
......
...@@ -54,7 +54,8 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -54,7 +54,8 @@ class Projects::MilestonesController < Projects::ApplicationController
format.js format.js
format.html do format.html do
if @milestone.valid? if @milestone.valid?
redirect_to [@project, @milestone] redirect_to namespace_project_milestone_path(@project.namespace,
@project, @milestone)
else else
render :edit render :edit
end end
......
...@@ -7,7 +7,7 @@ class Projects::RepositoriesController < Projects::ApplicationController ...@@ -7,7 +7,7 @@ class Projects::RepositoriesController < Projects::ApplicationController
def create def create
@project.create_repository @project.create_repository
redirect_to namespace_project_path(@project.namespace, @project) redirect_to project_path(@project)
end end
def archive def archive
......
...@@ -50,7 +50,8 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -50,7 +50,8 @@ class Projects::ServicesController < Projects::ApplicationController
:room, :recipients, :project_url, :webhook, :room, :recipients, :project_url, :webhook,
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password, :user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
:build_key, :server, :teamcity_url, :build_type, :build_key, :server, :teamcity_url, :build_type,
:description, :issues_url, :new_issue_url, :restrict_to_branch :description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
:colorize_messages, :channels
) )
end end
end end
class Projects::UploadsController < Projects::ApplicationController class Projects::UploadsController < Projects::ApplicationController
layout "project" layout 'project'
before_filter :project before_filter :project
def create
link_to_file = ::Projects::UploadService.new(project, params[:file]).
execute
respond_to do |format|
if link_to_file
format.json do
render json: { link: link_to_file }
end
else
format.json do
render json: 'Invalid file.', status: :unprocessable_entity
end
end
end
end
def show def show
path = File.join(project.path_with_namespace, params[:secret]) uploader = FileUploader.new(project, params[:secret])
uploader = FileUploader.new('uploads', path)
return redirect_to uploader.url unless uploader.file_storage?
uploader.retrieve_from_store!(params[:filename]) uploader.retrieve_from_store!(params[:filename])
if uploader.file.exists? return not_found! unless uploader.file.exists?
# Right now, these are always images, so we can safely render them inline.
send_file uploader.file.path, disposition: 'inline' disposition = uploader.image? ? 'inline' : 'attachment'
else send_file uploader.file.path, disposition: disposition
not_found!
end
end end
end end
...@@ -23,7 +23,7 @@ class ProjectsController < ApplicationController ...@@ -23,7 +23,7 @@ class ProjectsController < ApplicationController
if @project.saved? if @project.saved?
redirect_to( redirect_to(
namespace_project_path(@project.namespace, @project), project_path(@project),
notice: 'Project was successfully created.' notice: 'Project was successfully created.'
) )
else else
...@@ -39,7 +39,7 @@ class ProjectsController < ApplicationController ...@@ -39,7 +39,7 @@ class ProjectsController < ApplicationController
flash[:notice] = 'Project was successfully updated.' flash[:notice] = 'Project was successfully updated.'
format.html do format.html do
redirect_to( redirect_to(
edit_namespace_project_path(@project.namespace, @project), edit_project_path(@project),
notice: 'Project was successfully updated.' notice: 'Project was successfully updated.'
) )
end end
...@@ -102,7 +102,7 @@ class ProjectsController < ApplicationController ...@@ -102,7 +102,7 @@ class ProjectsController < ApplicationController
flash[:alert] = 'Project deleted.' flash[:alert] = 'Project deleted.'
if request.referer.include?('/admin') if request.referer.include?('/admin')
redirect_to admin_namespace_projects_path redirect_to admin_namespaces_projects_path
else else
redirect_to projects_dashboard_path redirect_to projects_dashboard_path
end end
...@@ -133,7 +133,7 @@ class ProjectsController < ApplicationController ...@@ -133,7 +133,7 @@ class ProjectsController < ApplicationController
@project.archive! @project.archive!
respond_to do |format| respond_to do |format|
format.html { redirect_to namespace_project_path(@project.namespace, @project) } format.html { redirect_to project_path(@project) }
end end
end end
...@@ -142,19 +142,7 @@ class ProjectsController < ApplicationController ...@@ -142,19 +142,7 @@ class ProjectsController < ApplicationController
@project.unarchive! @project.unarchive!
respond_to do |format| respond_to do |format|
format.html { redirect_to namespace_project_path(@project.namespace, @project) } format.html { redirect_to project_path(@project) }
end
end
def upload_image
link_to_image = ::Projects::ImageService.new(repository, params, root_url).execute
respond_to do |format|
if link_to_image
format.json { render json: { link: link_to_image } }
else
format.json { render json: 'Invalid file.', status: :unprocessable_entity }
end
end end
end end
...@@ -170,15 +158,6 @@ class ProjectsController < ApplicationController ...@@ -170,15 +158,6 @@ class ProjectsController < ApplicationController
private private
def upload_path
base_dir = FileUploader.generate_dir
File.join(repository.path_with_namespace, base_dir)
end
def accepted_images
%w(png jpg jpeg gif)
end
def set_title def set_title
@title = 'New Project' @title = 'New Project'
end end
......
class UploadsController < ApplicationController class UploadsController < ApplicationController
skip_before_filter :authenticate_user!, :reject_blocked skip_before_filter :authenticate_user!, :reject_blocked!
before_filter :authorize_access before_filter :authorize_access
def show def show
model = params[:model].camelize.constantize.find(params[:id]) model = params[:model].camelize.constantize.find(params[:id])
uploader = model.send(params[:mounted_as]) uploader = model.send(params[:mounted_as])
if uploader.file_storage? return not_found! if model.respond_to?(:project) && !can?(current_user, :read_project, model.project)
if !model.respond_to?(:project) || can?(current_user, :read_project, model.project)
disposition = uploader.image? ? 'inline' : 'attachment' return redirect_to uploader.url unless uploader.file_storage?
send_file uploader.file.path, disposition: disposition
else return not_found! unless uploader.file.exists?
not_found!
end disposition = uploader.image? ? 'inline' : 'attachment'
else send_file uploader.file.path, disposition: disposition
redirect_to uploader.url
end
end end
def authorize_access def authorize_access
unless params[:mounted_as] == 'avatar' unless params[:mounted_as] == 'avatar'
authenticate_user! && reject_blocked authenticate_user! && reject_blocked!
end end
end end
end end
...@@ -6,7 +6,9 @@ class UsersController < ApplicationController ...@@ -6,7 +6,9 @@ class UsersController < ApplicationController
def show def show
@contributed_projects = Project. @contributed_projects = Project.
where(id: authorized_projects_ids & @user.contributed_projects_ids). where(id: authorized_projects_ids & @user.contributed_projects_ids).
in_group_namespace.includes(:namespace) in_group_namespace.
includes(:namespace).
reject(&:forked?)
@projects = @user.personal_projects. @projects = @user.personal_projects.
where(id: authorized_projects_ids).includes(:namespace) where(id: authorized_projects_ids).includes(:namespace)
......
...@@ -44,7 +44,7 @@ class IssuableFinder ...@@ -44,7 +44,7 @@ class IssuableFinder
table_name = klass.table_name table_name = klass.table_name
if project if project
if project.public? || (current_user && current_user.can?(:read_project, project)) if Ability.abilities.allowed?(current_user, :read_project, project)
project.send(table_name) project.send(table_name)
else else
[] []
......
...@@ -37,4 +37,8 @@ module AppearancesHelper ...@@ -37,4 +37,8 @@ module AppearancesHelper
def brand_item def brand_item
@appearance ||= Appearance.first @appearance ||= Appearance.first
end end
def brand_header_logo
image_tag 'logo-white.png'
end
end end
...@@ -58,10 +58,8 @@ module ApplicationHelper ...@@ -58,10 +58,8 @@ module ApplicationHelper
Project.find_with_namespace(project_id) Project.find_with_namespace(project_id)
end end
if project.avatar.present? if project.avatar_url
image_tag project.avatar.url, options image_tag project.avatar_url, options
elsif project.avatar_in_git
image_tag namespace_project_avatar_path(project.namespace, project), options
else # generated icon else # generated icon
project_identicon(project, options) project_identicon(project, options)
end end
......
...@@ -30,7 +30,7 @@ module EventsHelper ...@@ -30,7 +30,7 @@ module EventsHelper
end end
content_tag :li, class: "filter_icon #{active}" do content_tag :li, class: "filter_icon #{active}" do
link_to request.path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => tooltip do link_to request.path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => 'Filter by ' + tooltip.downcase do
icon(icon_for_event[key]) + content_tag(:span, ' ' + tooltip) icon(icon_for_event[key]) + content_tag(:span, ' ' + tooltip)
end end
end end
......
# Shorter routing method for project and project items
# Since update to rails 4.1.9 we are now allowed to use `/` in project routing
# so we use nested routing for project resources which include project and
# project namespace. To avoid writing long methods every time we define shortcuts for
# some of routing.
#
# For example instead of this:
#
# namespace_project_merge_request_path(merge_request.project.namespace, merge_request.projects, merge_request)
#
# We can simply use shortcut:
#
# merge_request_path(merge_request)
#
module GitlabRoutingHelper
def project_path(project, *args)
namespace_project_path(project.namespace, project, *args)
end
def edit_project_path(project, *args)
edit_namespace_project_path(project.namespace, project, *args)
end
def issue_path(entity, *args)
namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args)
end
def merge_request_path(entity, *args)
namespace_project_merge_request_path(entity.project.namespace, entity.project, entity, *args)
end
def project_url(project, *args)
namespace_project_url(project.namespace, project, *args)
end
def edit_project_url(project, *args)
edit_namespace_project_url(project.namespace, project, *args)
end
def issue_url(entity, *args)
namespace_project_issue_url(entity.project.namespace, entity.project, entity, *args)
end
def merge_request_url(entity, *args)
namespace_project_merge_request_url(entity.project.namespace, entity.project, entity, *args)
end
end
...@@ -12,7 +12,7 @@ module OauthHelper ...@@ -12,7 +12,7 @@ module OauthHelper
end end
def default_providers def default_providers
[:twitter, :github, :gitlab, :google_oauth2, :ldap] [:twitter, :github, :gitlab, :bitbucket, :google_oauth2, :ldap]
end end
def enabled_oauth_providers def enabled_oauth_providers
...@@ -21,13 +21,15 @@ module OauthHelper ...@@ -21,13 +21,15 @@ module OauthHelper
def enabled_social_providers def enabled_social_providers
enabled_oauth_providers.select do |name| enabled_oauth_providers.select do |name|
[:twitter, :gitlab, :github, :google_oauth2, :kerberos,].include?(name.to_sym) [:twitter, :gitlab, :github, :bitbucket, :google_oauth2, :kerberos].include?(name.to_sym)
end end
end end
def additional_providers def additional_providers
enabled_oauth_providers.reject do |provider| enabled_oauth_providers.reject do |provider|
provider.to_s.starts_with?('ldap') || provider == :kerberos provider.to_s.starts_with?('ldap') || provider == :kerberos
end end
end end
extend self
end end
...@@ -46,7 +46,7 @@ module ProjectsHelper ...@@ -46,7 +46,7 @@ module ProjectsHelper
simple_sanitize(project.group.name), group_path(project.group) simple_sanitize(project.group.name), group_path(project.group)
) + ' / ' + ) + ' / ' +
link_to(simple_sanitize(project.name), link_to(simple_sanitize(project.name),
namespace_project_path(project.namespace, project)) project_path(project))
end end
else else
owner = project.namespace.owner owner = project.namespace.owner
...@@ -55,7 +55,7 @@ module ProjectsHelper ...@@ -55,7 +55,7 @@ module ProjectsHelper
simple_sanitize(owner.name), user_path(owner) simple_sanitize(owner.name), user_path(owner)
) + ' / ' + ) + ' / ' +
link_to(simple_sanitize(project.name), link_to(simple_sanitize(project.name),
namespace_project_path(project.namespace, project)) project_path(project))
end end
end end
end end
......
...@@ -26,7 +26,7 @@ class Group < Namespace ...@@ -26,7 +26,7 @@ class Group < Namespace
validate :avatar_type, if: ->(user) { user.avatar_changed? } validate :avatar_type, if: ->(user) { user.avatar_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i } validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
mount_uploader :avatar, AttachmentUploader mount_uploader :avatar, AvatarUploader
after_create :post_create_hook after_create :post_create_hook
after_destroy :post_destroy_hook after_destroy :post_destroy_hook
......
...@@ -37,6 +37,8 @@ class Project < ActiveRecord::Base ...@@ -37,6 +37,8 @@ class Project < ActiveRecord::Base
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
include Gitlab::VisibilityLevel include Gitlab::VisibilityLevel
include Gitlab::ConfigHelper include Gitlab::ConfigHelper
include Rails.application.routes.url_helpers
extend Gitlab::ConfigHelper extend Gitlab::ConfigHelper
extend Enumerize extend Enumerize
...@@ -48,6 +50,12 @@ class Project < ActiveRecord::Base ...@@ -48,6 +50,12 @@ class Project < ActiveRecord::Base
default_value_for :wall_enabled, false default_value_for :wall_enabled, false
default_value_for :snippets_enabled, gitlab_config_features.snippets default_value_for :snippets_enabled, gitlab_config_features.snippets
# set last_activity_at to the same as created_at
after_create :set_last_activity_at
def set_last_activity_at
update_column(:last_activity_at, self.created_at)
end
ActsAsTaggableOn.strict_case_match = true ActsAsTaggableOn.strict_case_match = true
acts_as_taggable_on :tags acts_as_taggable_on :tags
...@@ -66,6 +74,7 @@ class Project < ActiveRecord::Base ...@@ -66,6 +74,7 @@ class Project < ActiveRecord::Base
has_one :gitlab_ci_service, dependent: :destroy has_one :gitlab_ci_service, dependent: :destroy
has_one :campfire_service, dependent: :destroy has_one :campfire_service, dependent: :destroy
has_one :emails_on_push_service, dependent: :destroy has_one :emails_on_push_service, dependent: :destroy
has_one :irker_service, dependent: :destroy
has_one :pivotaltracker_service, dependent: :destroy has_one :pivotaltracker_service, dependent: :destroy
has_one :hipchat_service, dependent: :destroy has_one :hipchat_service, dependent: :destroy
has_one :flowdock_service, dependent: :destroy has_one :flowdock_service, dependent: :destroy
...@@ -136,7 +145,7 @@ class Project < ActiveRecord::Base ...@@ -136,7 +145,7 @@ class Project < ActiveRecord::Base
validates_uniqueness_of :name, scope: :namespace_id validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id validates_uniqueness_of :path, scope: :namespace_id
validates :import_url, validates :import_url,
format: { with: URI::regexp(%w(git http https)), message: 'should be a valid url' }, format: { with: URI::regexp(%w(ssh git http https)), message: 'should be a valid url' },
if: :import? if: :import?
validates :star_count, numericality: { greater_than_or_equal_to: 0 } validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create validate :check_limit, on: :create
...@@ -144,7 +153,7 @@ class Project < ActiveRecord::Base ...@@ -144,7 +153,7 @@ class Project < ActiveRecord::Base
if: ->(project) { project.avatar && project.avatar_changed? } if: ->(project) { project.avatar && project.avatar_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i } validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
mount_uploader :avatar, AttachmentUploader mount_uploader :avatar, AvatarUploader
# Scopes # Scopes
scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) } scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) }
...@@ -416,6 +425,14 @@ class Project < ActiveRecord::Base ...@@ -416,6 +425,14 @@ class Project < ActiveRecord::Base
@avatar_file @avatar_file
end end
def avatar_url
if avatar.present?
[gitlab_config.url, avatar.url].join
elsif avatar_in_git
[gitlab_config.url, namespace_project_avatar_path(namespace, self)].join
end
end
# For compatibility with old code # For compatibility with old code
def code def code
path path
......
# == 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)
require 'uri'
class IrkerService < Service
prop_accessor :colorize_messages, :recipients, :channels
validates :recipients, presence: true, if: :activated?
validate :check_recipients_count, if: :activated?
before_validation :get_channels
after_initialize :initialize_settings
# Writer for RSpec tests
attr_writer :settings
def initialize_settings
# See the documentation (doc/project_services/irker.md) for possible values
# here
@settings ||= {
server_ip: 'localhost',
server_port: 6659,
max_channels: 3,
default_irc_uri: nil
}
end
def title
'Irker (IRC gateway)'
end
def description
'Send IRC messages, on update, to a list of recipients through an Irker '\
'gateway.'
end
def help
msg = 'Recipients have to be specified with a full URI: '\
'irc[s]://irc.network.net[:port]/#channel. Special cases: if you want '\
'the channel to be a nickname instead, append ",isnick" to the channel '\
'name; if the channel is protected by a secret password, append '\
'"?key=secretpassword" to the URI.'
unless @settings[:default_irc].nil?
msg += ' Note that a default IRC URI is provided by this service\'s '\
"administrator: #{default_irc}. You can thus just give a channel name."
end
msg
end
def to_param
'irker'
end
def execute(push_data)
IrkerWorker.perform_async(project_id, channels,
colorize_messages, push_data, @settings)
end
def fields
[
{ type: 'textarea', name: 'recipients',
placeholder: 'Recipients/channels separated by whitespaces' },
{ type: 'checkbox', name: 'colorize_messages' },
]
end
private
def check_recipients_count
return true if recipients.nil? || recipients.empty?
if recipients.split(/\s+/).count > max_chans
errors.add(:recipients, "are limited to #{max_chans}")
end
end
def max_chans
@settings[:max_channels]
end
def get_channels
return true unless :activated?
return true if recipients.nil? || recipients.empty?
map_recipients
errors.add(:recipients, 'are all invalid') if channels.empty?
true
end
def map_recipients
self.channels = recipients.split(/\s+/).map do |recipient|
format_channel default_irc_uri, recipient
end
channels.reject! &:nil?
end
def default_irc_uri
default_irc = @settings[:default_irc_uri]
if !(default_irc.nil? || default_irc[-1] == '/')
default_irc += '/'
end
default_irc
end
def format_channel(default_irc, recipient)
cnt = 0
url = nil
# Try to parse the chan as a full URI
begin
uri = URI.parse(recipient)
raise URI::InvalidURIError if uri.scheme.nil? && cnt == 0
rescue URI::InvalidURIError
unless default_irc.nil?
cnt += 1
recipient = "#{default_irc}#{recipient}"
retry if cnt == 1
end
else
url = consider_uri uri
end
url
end
def consider_uri(uri)
# Authorize both irc://domain.com/#chan and irc://domain.com/chan
if uri.is_a?(URI) && uri.scheme[/^ircs?$/] && !uri.path.nil?
# Do not authorize irc://domain.com/
if uri.fragment.nil? && uri.path.length > 1
uri.to_s
else
# Authorize irc://domain.com/smthg#chan
# The irker daemon will deal with it by concatenating smthg and
# chan, thus sending messages on #smthgchan
uri.to_s
end
end
end
end
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
# #
class SlackService < Service class SlackService < Service
prop_accessor :webhook prop_accessor :webhook, :username, :channel
validates :webhook, presence: true, if: :activated? validates :webhook, presence: true, if: :activated?
def title def title
...@@ -31,7 +31,10 @@ class SlackService < Service ...@@ -31,7 +31,10 @@ class SlackService < Service
def fields def fields
[ [
{ type: 'text', name: 'webhook', placeholder: 'https://hooks.slack.com/services/...' } { type: 'text', name: 'webhook',
placeholder: 'https://hooks.slack.com/services/...' },
{ type: 'text', name: 'username', placeholder: 'username' },
{ type: 'text', name: 'channel', placeholder: '#channel' }
] ]
end end
...@@ -43,7 +46,11 @@ class SlackService < Service ...@@ -43,7 +46,11 @@ class SlackService < Service
project_name: project_name project_name: project_name
)) ))
notifier = Slack::Notifier.new(webhook) opt = {}
opt[:channel] = channel if channel
opt[:username] = username if username
notifier = Slack::Notifier.new(webhook, opt)
notifier.ping(message.pretext, attachments: message.attachments) notifier.ping(message.pretext, attachments: message.attachments)
end end
......
...@@ -115,13 +115,13 @@ class TeamcityService < CiService ...@@ -115,13 +115,13 @@ class TeamcityService < CiService
end end
end end
def execute(data) def execute(push)
auth = { auth = {
username: username, username: username,
password: password, password: password,
} }
branch = data[:ref] branch = push[:ref].gsub('refs/heads/', '')
self.class.post("#{teamcity_url}/httpAuth/app/rest/buildQueue", self.class.post("#{teamcity_url}/httpAuth/app/rest/buildQueue",
body: "<build branchName=\"#{branch}\">"\ body: "<build branchName=\"#{branch}\">"\
......
...@@ -238,7 +238,7 @@ class Repository ...@@ -238,7 +238,7 @@ class Repository
end end
def last_commit_for_path(sha, path) def last_commit_for_path(sha, path)
args = %W(git rev-list --max-count 1 #{sha} -- #{path}) args = %W(git rev-list --max-count=1 #{sha} -- #{path})
sha = Gitlab::Popen.popen(args, path_to_repo).first.strip sha = Gitlab::Popen.popen(args, path_to_repo).first.strip
commit(sha) commit(sha)
end end
......
...@@ -118,6 +118,7 @@ class Service < ActiveRecord::Base ...@@ -118,6 +118,7 @@ class Service < ActiveRecord::Base
jira jira
redmine redmine
custom_issue_tracker custom_issue_tracker
irker
) )
end end
......
...@@ -46,6 +46,7 @@ ...@@ -46,6 +46,7 @@
# github_access_token :string(255) # github_access_token :string(255)
# notification_email :string(255) # notification_email :string(255)
# password_automatically_set :boolean default(FALSE) # password_automatically_set :boolean default(FALSE)
# bitbucket_access_token :string(255)
# #
require 'carrierwave/orm/activerecord' require 'carrierwave/orm/activerecord'
...@@ -178,7 +179,7 @@ class User < ActiveRecord::Base ...@@ -178,7 +179,7 @@ class User < ActiveRecord::Base
end end
end end
mount_uploader :avatar, AttachmentUploader mount_uploader :avatar, AvatarUploader
# Scopes # Scopes
scope :admins, -> { where(admin: true) } scope :admins, -> { where(admin: true) }
...@@ -639,9 +640,11 @@ class User < ActiveRecord::Base ...@@ -639,9 +640,11 @@ class User < ActiveRecord::Base
def contributed_projects_ids def contributed_projects_ids
Event.where(author_id: self). Event.where(author_id: self).
where("created_at > ?", Time.now - 1.year). where("created_at > ?", Time.now - 1.year).
code_push. where("action = :pushed OR (target_type = 'MergeRequest' AND action = :created)",
pushed: Event::PUSHED, created: Event::CREATED).
reorder(project_id: :desc). reorder(project_id: :desc).
select('DISTINCT(project_id)'). select(:project_id).
map(&:project_id) uniq
.map(&:project_id)
end end
end end
...@@ -42,7 +42,8 @@ module Files ...@@ -42,7 +42,8 @@ module Files
created_successfully = new_file_action.commit!( created_successfully = new_file_action.commit!(
params[:content], params[:content],
params[:commit_message], params[:commit_message],
params[:encoding] params[:encoding],
params[:new_branch]
) )
if created_successfully if created_successfully
......
...@@ -27,7 +27,8 @@ module Files ...@@ -27,7 +27,8 @@ module Files
edit_file_action.commit!( edit_file_action.commit!(
params[:content], params[:content],
params[:commit_message], params[:commit_message],
params[:encoding] params[:encoding],
params[:new_branch]
) )
success success
......
module Projects
class ImageService < BaseService
include Rails.application.routes.url_helpers
def initialize(repository, params, root_url)
@repository, @params, @root_url = repository, params.dup, root_url
end
def execute
uploader = FileUploader.new('uploads', upload_path, accepted_images)
image = @params['markdown_img']
if image && correct_mime_type?(image)
alt = image.original_filename
uploader.store!(image)
link = {
'alt' => File.basename(alt, '.*'),
'url' => File.join(@root_url, uploader.url)
}
else
link = nil
end
end
protected
def upload_path
base_dir = FileUploader.generate_dir
File.join(@repository.path_with_namespace, base_dir)
end
def accepted_images
%w(png jpg jpeg gif)
end
def correct_mime_type?(image)
accepted_images.map{ |format| image.content_type.include? format }.any?
end
end
end
...@@ -35,15 +35,21 @@ module Projects ...@@ -35,15 +35,21 @@ module Projects
end end
def sorted(users) def sorted(users)
users.uniq.to_a.compact.sort_by(&:username).map { |user| { username: user.username, name: user.name } } users.uniq.to_a.compact.sort_by(&:username).map do |user|
{ username: user.username, name: user.name }
end
end end
def groups def groups
@user.authorized_groups.sort_by(&:path).map { |group| { username: group.path, name: group.name } } @user.authorized_groups.sort_by(&:path).map do |group|
count = group.users.count
{ username: group.path, name: "#{group.name} (#{count})" }
end
end end
def all_members def all_members
[{ username: "all", name: "Project and Group Members" }] count = @project.team.members.flatten.count
[{ username: "all", name: "All Project and Group Members (#{count})" }]
end end
end end
end end
module Projects
class UploadService < BaseService
def initialize(project, file)
@project, @file = project, file
end
def execute
return nil unless @file
uploader = FileUploader.new(@project)
uploader.store!(@file)
filename = uploader.image? ? uploader.file.basename : uploader.file.filename
{
'alt' => filename,
'url' => uploader.secure_url,
'is_image' => uploader.image?
}
end
end
end
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
class AttachmentUploader < CarrierWave::Uploader::Base class AttachmentUploader < CarrierWave::Uploader::Base
storage :file storage :file
after :store, :reset_events_cache
def store_dir def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end end
...@@ -22,15 +20,7 @@ class AttachmentUploader < CarrierWave::Uploader::Base ...@@ -22,15 +20,7 @@ class AttachmentUploader < CarrierWave::Uploader::Base
false false
end end
def secure_url
Gitlab.config.gitlab.relative_url_root + "/files/#{model.class.to_s.underscore}/#{model.id}/#{file.filename}"
end
def file_storage? def file_storage?
self.class.storage == CarrierWave::Storage::File self.class.storage == CarrierWave::Storage::File
end end
def reset_events_cache(file)
model.reset_events_cache if model.is_a?(User)
end
end end
# encoding: utf-8
class AvatarUploader < CarrierWave::Uploader::Base
storage :file
after :store, :reset_events_cache
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def image?
img_ext = %w(png jpg jpeg gif bmp tiff)
if file.respond_to?(:extension)
img_ext.include?(file.extension.downcase)
else
# Not all CarrierWave storages respond to :extension
ext = file.path.split('.').last.downcase
img_ext.include?(ext)
end
rescue
false
end
def file_storage?
self.class.storage == CarrierWave::Storage::File
end
def reset_events_cache(file)
model.reset_events_cache if model.is_a?(User)
end
end
...@@ -2,40 +2,47 @@ ...@@ -2,40 +2,47 @@
class FileUploader < CarrierWave::Uploader::Base class FileUploader < CarrierWave::Uploader::Base
storage :file storage :file
def initialize(base_dir, path = '', allowed_extensions = nil) attr_accessor :project, :secret
@base_dir = base_dir
@path = path def initialize(project, secret = self.class.generate_secret)
@allowed_extensions = allowed_extensions @project = project
@secret = secret
end end
def base_dir def base_dir
@base_dir "uploads"
end end
def store_dir def store_dir
File.join(@base_dir, @path) File.join(base_dir, @project.path_with_namespace, @secret)
end end
def cache_dir def cache_dir
File.join(@base_dir, 'tmp', @path) File.join(base_dir, 'tmp', @project.path_with_namespace, @secret)
end end
def extension_white_list def self.generate_secret
@allowed_extensions SecureRandom.hex
end end
def store!(file) def secure_url
@filename = self.class.generate_filename(file) File.join(Gitlab.config.gitlab.url, @project.path_with_namespace, "uploads", @secret, file.filename)
super
end end
def self.generate_filename(file) def file_storage?
original_filename = File.basename(file.original_filename, '.*') self.class.storage == CarrierWave::Storage::File
extension = File.extname(file.original_filename)
new_filename = Digest::MD5.hexdigest(original_filename) + extension
end end
def self.generate_dir def image?
SecureRandom.hex(5) img_ext = %w(png jpg jpeg gif bmp tiff)
if file.respond_to?(:extension)
img_ext.include?(file.extension.downcase)
else
# Not all CarrierWave storages respond to :extension
ext = file.path.split('.').last.downcase
img_ext.include?(ext)
end
rescue
false
end end
end end
.row .row
.col-md-3 = link_to '#aside', class: 'show-aside' do
%i.fa.fa-angle-left
%aside.col-md-3
.admin-filter .admin-filter
= form_tag admin_namespaces_projects_path, method: :get, class: '' do = form_tag admin_namespaces_projects_path, method: :get, class: '' do
.form-group .form-group
...@@ -36,7 +38,7 @@ ...@@ -36,7 +38,7 @@
= button_tag "Search", class: "btn submit btn-primary" = button_tag "Search", class: "btn submit btn-primary"
= link_to "Reset", admin_namespaces_projects_path, class: "btn btn-cancel" = link_to "Reset", admin_namespaces_projects_path, class: "btn btn-cancel"
.col-md-9 %section.col-md-9
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
Projects (#{@projects.total_count}) Projects (#{@projects.total_count})
......
%h3.page-title %h3.page-title
Project: #{@project.name_with_namespace} Project: #{@project.name_with_namespace}
= link_to edit_namespace_project_path(@project.namespace, @project), class: "btn pull-right" do = link_to edit_project_path(@project), class: "btn pull-right" do
%i.fa.fa-pencil-square-o %i.fa.fa-pencil-square-o
Edit Edit
%hr %hr
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
%li %li
%span.light Name: %span.light Name:
%strong %strong
= link_to @project.name, namespace_project_path(@project.namespace, @project) = link_to @project.name, project_path(@project)
%li %li
%span.light Namespace: %span.light Namespace:
%strong %strong
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
%tr %tr
%th %th
%th Service %th Service
%th Desription %th Description
%th Last edit %th Last edit
- @services.sort_by(&:title).each do |service| - @services.sort_by(&:title).each do |service|
%tr %tr
......
.row .row
.col-md-3 = link_to '#aside', class: 'show-aside' do
%i.fa.fa-angle-left
%aside.col-md-3
.admin-filter .admin-filter
%ul.nav.nav-pills.nav-stacked %ul.nav.nav-pills.nav-stacked
%li{class: "#{'active' unless params[:filter]}"} %li{class: "#{'active' unless params[:filter]}"}
...@@ -27,7 +29,7 @@ ...@@ -27,7 +29,7 @@
%hr %hr
= link_to 'Reset', admin_users_path, class: "btn btn-cancel" = link_to 'Reset', admin_users_path, class: "btn btn-cancel"
.col-md-9 %section.col-md-9
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
Users (#{@users.total_count}) Users (#{@users.total_count})
......
= link_to namespace_project_path(project.namespace, project), class: dom_class(project) do = link_to project_path(project), class: dom_class(project) do
.dash-project-avatar .dash-project-avatar
= project_icon(project, alt: '', class: 'avatar project-avatar s40') = project_icon(project, alt: '', class: 'avatar project-avatar s40')
.dash-project-access-icon .dash-project-access-icon
......
...@@ -16,10 +16,10 @@ ...@@ -16,10 +16,10 @@
%li.my-project-row %li.my-project-row
%h4.project-title %h4.project-title
.pull-left .pull-left
= project_icon("#{project.namespace.to_param}/#{project.to_param}", alt: '', class: 'avatar project-avatar s60') = project_icon(project, alt: '', class: 'avatar project-avatar s60')
.project-access-icon .project-access-icon
= visibility_level_icon(project.visibility_level) = visibility_level_icon(project.visibility_level)
= link_to namespace_project_path(project.namespace, project), class: dom_class(project) do = link_to project_path(project), class: dom_class(project) do
%strong= project.name_with_namespace %strong= project.name_with_namespace
- if project.forked_from_project - if project.forked_from_project
......
%div %div
= render 'devise/shared/signin_box' - if signin_enabled? || ldap_enabled?
= render 'devise/shared/signin_box'
- if signup_enabled? -# Omniauth fits between signin/ldap signin and signup and does not have a surrounding box
- if Gitlab.config.omniauth.enabled && devise_mapping.omniauthable?
.clearfix.prepend-top-20
= render 'devise/shared/omniauth_box'
-# Signup only makes sense if you can also sign-in
- if signin_enabled? && signup_enabled?
.prepend-top-20 .prepend-top-20
= render 'devise/shared/signup_box' = render 'devise/shared/signup_box'
-# Show a message if none of the mechanisms above are enabled
- if !signin_enabled? && !ldap_enabled? && !(Gitlab.config.omniauth.enabled && devise_mapping.omniauthable?)
%div
No authentication methods configured.
%p
%span.light
Sign in with &nbsp;
- providers = additional_providers
- providers.each do |provider|
%span.light
- if default_providers.include?(provider)
= link_to authbutton(provider, 32), omniauth_authorize_path(resource_name, provider)
- else
= link_to provider.to_s.titleize, omniauth_authorize_path(resource_name, provider), class: "btn"
...@@ -18,9 +18,9 @@ ...@@ -18,9 +18,9 @@
- note = event.target - note = event.target
- if note.attachment.url - if note.attachment.url
- if note.attachment.image? - if note.attachment.image?
= link_to note.attachment.secure_url, target: '_blank' do = link_to note.attachment.url, target: '_blank' do
= image_tag note.attachment.secure_url, class: 'note-image-attach' = image_tag note.attachment.url, class: 'note-image-attach'
- else - else
= link_to note.attachment.secure_url, target: "_blank", class: 'note-file-attach' do = link_to note.attachment.url, target: "_blank", class: 'note-file-attach' do
%i.fa.fa-paperclip %i.fa.fa-paperclip
= note.attachment_identifier = note.attachment_identifier
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
.nothing-here-block This group has no projects yet .nothing-here-block This group has no projects yet
- projects.each do |project| - projects.each do |project|
%li.project-row %li.project-row
= link_to namespace_project_path(project.namespace, project), class: dom_class(project) do = link_to project_path(project), class: dom_class(project) do
.dash-project-avatar .dash-project-avatar
= project_icon(project, alt: '', class: 'avatar s40') = project_icon(project, alt: '', class: 'avatar s40')
.dash-project-access-icon .dash-project-access-icon
......
...@@ -10,9 +10,16 @@ ...@@ -10,9 +10,16 @@
target_field.append("/" + project_name) target_field.append("/" + project_name)
target_field.data("project_name", project_name) target_field.data("project_name", project_name)
target_field.find('input').prop("value", origin_namespace) target_field.find('input').prop("value", origin_namespace)
- elsif @access_denied
:plain
job = $("tr#repo_#{@repo_id}")
job.find(".import-actions").html("<p class='alert alert-danger'>Access denied! Please verify you can add deploy keys to this repository.</p>"")
- else - else
:plain :plain
job = $("tr#repo_#{@repo_id}") job = $("tr#repo_#{@repo_id}")
job.attr("id", "project_#{@project.id}") job.attr("id", "project_#{@project.id}")
target_field = job.find(".import-target")
target_field.empty()
target_field.append('<strong>#{link_to @project.path_with_namespace, [@project.namespace.becomes(Namespace), @project]}</strong>')
$("table.import-jobs tbody").prepend(job) $("table.import-jobs tbody").prepend(job)
job.addClass("active").find(".import-actions").html("<i class='fa fa-spinner fa-spin'></i> started") job.addClass("active").find(".import-actions").html("<i class='fa fa-spinner fa-spin'></i> started")
%h3.page-title
%i.fa.fa-bitbucket
Import projects from Bitbucket
%p.light
Select projects you want to import.
%hr
%p
= button_tag 'Import all projects', class: "btn btn-success js-import-all"
%table.table.import-jobs
%thead
%tr
%th From Bitbucket
%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
= link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: "_blank"
%td
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status
- if project.import_status == 'finished'
%span.cgreen
%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["owner"]}___#{repo["slug"]}"}
%td
= link_to "#{repo["owner"]}/#{repo["slug"]}", "https://bitbucket.org/#{repo["owner"]}/#{repo["slug"]}", target: "_blank"
%td.import-target
= "#{repo["owner"]}/#{repo["slug"]}"
%td.import-actions.job-status
= button_tag "Import", class: "btn js-add-to-import"
:coffeescript
$ ->
new ImporterStatus("#{jobs_import_bitbucket_path}", "#{import_bitbucket_path}")
%h3.page-title %h3.page-title
%i.fa.fa-github %i.fa.fa-github
Import repositories from GitHub.com Import projects from GitHub
%p.light %p.light
Select projects you want to import. Select projects you want to import.
...@@ -17,20 +17,25 @@ ...@@ -17,20 +17,25 @@
%tbody %tbody
- @already_added_projects.each do |project| - @already_added_projects.each do |project|
%tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
%td= project.import_source
%td %td
%strong= link_to project.path_with_namespace, project = link_to project.import_source, "https://github.com/#{project.import_source}", target: "_blank"
%td
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status %td.job-status
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span.cgreen %span.cgreen
%i.fa.fa-check %i.fa.fa-check
done done
- elsif project.import_status == 'started'
%i.fa.fa-spinner.fa-spin
started
- else - else
= project.human_import_status_name = project.human_import_status_name
- @repos.each do |repo| - @repos.each do |repo|
%tr{id: "repo_#{repo.id}"} %tr{id: "repo_#{repo.id}"}
%td= repo.full_name %td
= link_to repo.full_name, "https://github.com/#{repo.full_name}", target: "_blank"
%td.import-target %td.import-target
= repo.full_name = repo.full_name
%td.import-actions.job-status %td.import-actions.job-status
......
%h3.page-title %h3.page-title
%i.fa.fa-github %i.fa.fa-heart
Import repositories from GitLab.com Import projects from GitLab.com
%p.light %p.light
Select projects you want to import. Select projects you want to import.
...@@ -12,25 +12,30 @@ ...@@ -12,25 +12,30 @@
%thead %thead
%tr %tr
%th From GitLab.com %th From GitLab.com
%th To GitLab private instance %th To this GitLab instance
%th Status %th Status
%tbody %tbody
- @already_added_projects.each do |project| - @already_added_projects.each do |project|
%tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
%td= project.import_source
%td %td
%strong= link_to project.path_with_namespace, project = link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank"
%td
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status %td.job-status
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span.cgreen %span.cgreen
%i.fa.fa-check %i.fa.fa-check
done done
- elsif project.import_status == 'started'
%i.fa.fa-spinner.fa-spin
started
- else - else
= project.human_import_status_name = project.human_import_status_name
- @repos.each do |repo| - @repos.each do |repo|
%tr{id: "repo_#{repo["id"]}"} %tr{id: "repo_#{repo["id"]}"}
%td= repo["path_with_namespace"] %td
= link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank"
%td.import-target %td.import-target
= repo["path_with_namespace"] = repo["path_with_namespace"]
%td.import-actions.job-status %td.import-actions.job-status
......
%h3.page-title %h3.page-title
%i.fa.fa-gitorious %i.icon-gitorious.icon-gitorious-big
Import repositories from Gitorious.org Import projects from Gitorious.org
%p.light %p.light
Select projects you want to import. Select projects you want to import.
...@@ -11,26 +11,31 @@ ...@@ -11,26 +11,31 @@
%table.table.import-jobs %table.table.import-jobs
%thead %thead
%tr %tr
%th From Gitorious %th From Gitorious.org
%th To GitLab %th To GitLab
%th Status %th Status
%tbody %tbody
- @already_added_projects.each do |project| - @already_added_projects.each do |project|
%tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"}
%td= project.import_source
%td %td
%strong= link_to project.path_with_namespace, project = link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank"
%td
%strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project]
%td.job-status %td.job-status
- if project.import_status == 'finished' - if project.import_status == 'finished'
%span.cgreen %span.cgreen
%i.fa.fa-check %i.fa.fa-check
done done
- elsif project.import_status == 'started'
%i.fa.fa-spinner.fa-spin
started
- else - else
= project.human_import_status_name = project.human_import_status_name
- @repos.each do |repo| - @repos.each do |repo|
%tr{id: "repo_#{repo.id}"} %tr{id: "repo_#{repo.id}"}
%td= repo.full_name %td
= link_to repo.full_name, "https://gitorious.org/#{repo.full_name}", target: "_blank"
%td.import-target %td.import-target
= repo.full_name = repo.full_name
%td.import-actions.job-status %td.import-actions.job-status
......
- if nav_menu_collapsed? - if nav_menu_collapsed?
= link_to icon('angle-right'), '#', class: 'toggle-nav-collapse' = link_to icon('angle-right'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
- else - else
= link_to icon('angle-left'), '#', class: 'toggle-nav-collapse' = link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Open/Close"
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
%div.app_logo %div.app_logo
- brand_header_logo if brand_item - brand_header_logo if brand_item
= link_to root_path, class: "home has_bottom_tooltip", title: "Dashboard" do = link_to root_path, class: "home has_bottom_tooltip", title: "Dashboard" do
%h1 GITLAB = brand_header_logo
%h1.title= title %h1.title= title
%button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"} %button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"}
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
= link_to destroy_user_session_path, class: "logout", method: :delete, title: "Logout", class: 'has_bottom_tooltip', 'data-original-title' => 'Logout' do = link_to destroy_user_session_path, class: "logout", method: :delete, title: "Logout", class: 'has_bottom_tooltip', 'data-original-title' => 'Logout' do
%i.fa.fa-sign-out %i.fa.fa-sign-out
%li.hidden-xs %li.hidden-xs
= link_to current_user, class: "profile-pic", id: 'profile-pic' do = link_to current_user, class: "profile-pic has_bottom_tooltip", id: 'profile-pic', 'data-original-title' => 'Your profile' do
= image_tag avatar_icon(current_user.email, 60), alt: 'User activity' = image_tag avatar_icon(current_user.email, 60), alt: 'User activity'
= render 'shared/outdated_browser' = render 'shared/outdated_browser'
...@@ -2,10 +2,8 @@ ...@@ -2,10 +2,8 @@
.navbar-inner .navbar-inner
.container .container
%div.app_logo %div.app_logo
%span.separator
= link_to explore_root_path, class: "home" do = link_to explore_root_path, class: "home" do
%h1 GITLAB = brand_header_logo
%span.separator
%h1.title= title %h1.title= title
%button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"} %button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"}
......
!!! 5 !!! 5
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head", title: "Admin area" = render "layouts/head", title: "Admin area"
%body{class: "#{app_theme} #{theme_type} admin", :'data-page' => body_data_page} %body{class: "#{app_theme} admin", :'data-page' => body_data_page}
= render "layouts/head_panel", title: link_to("Admin area", admin_root_path) = render "layouts/head_panel", title: link_to("Admin area", admin_root_path)
= render 'layouts/page', sidebar: 'layouts/nav/admin' = render 'layouts/page', sidebar: 'layouts/nav/admin'
!!! 5 !!! 5
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head", title: "Dashboard" = render "layouts/head", title: "Dashboard"
%body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page } %body{class: "#{app_theme} application", :'data-page' => body_data_page }
= render "layouts/head_panel", title: link_to("Dashboard", root_path) = render "layouts/head_panel", title: link_to("Dashboard", root_path)
= render 'layouts/page', sidebar: 'layouts/nav/dashboard' = render 'layouts/page', sidebar: 'layouts/nav/dashboard'
!!! 5 !!! 5
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head", title: "Error" = render "layouts/head", title: "Error"
%body{class: "#{app_theme} #{theme_type} application"} %body{class: "#{app_theme} application"}
= render "layouts/head_panel", title: "" if current_user = render "layouts/head_panel", title: "" if current_user
.container.navless-container .container.navless-container
= render "layouts/flash" = render "layouts/flash"
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
!!! 5 !!! 5
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head", title: page_title = render "layouts/head", title: page_title
%body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} %body{class: "#{app_theme} application", :'data-page' => body_data_page}
= render "layouts/broadcast" = render "layouts/broadcast"
- if current_user - if current_user
= render "layouts/head_panel", title: link_to(page_title, explore_root_path) = render "layouts/head_panel", title: link_to(page_title, explore_root_path)
......
!!! 5 !!! 5
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head", title: group_head_title = render "layouts/head", title: group_head_title
%body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} %body{class: "#{app_theme} application", :'data-page' => body_data_page}
= render "layouts/head_panel", title: link_to(@group.name, group_path(@group)) = render "layouts/head_panel", title: link_to(@group.name, group_path(@group))
= render 'layouts/page', sidebar: 'layouts/nav/group' = render 'layouts/page', sidebar: 'layouts/nav/group'
%ul.project-navigation.nav.nav-sidebar %ul.project-navigation.nav.nav-sidebar
- if @project_settings_nav - if @project_settings_nav
= nav_link do = nav_link do
= link_to namespace_project_path(@project.namespace, @project), title: 'Back to project', class: "" do = link_to project_path(@project), title: 'Back to project', class: "" do
%i.fa.fa-angle-left %i.fa.fa-caret-square-o-left
%span %span
Back to project Back to project
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
- else - else
= nav_link(path: 'projects#show', html_options: {class: "home"}) do = nav_link(path: 'projects#show', html_options: {class: "home"}) do
= link_to namespace_project_path(@project.namespace, @project), title: 'Project', class: 'shortcuts-project' do = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
%i.fa.fa-dashboard %i.fa.fa-dashboard
%span %span
Project Project
...@@ -89,7 +89,7 @@ ...@@ -89,7 +89,7 @@
- if project_nav_tab? :settings - if project_nav_tab? :settings
= nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do = nav_link(html_options: {class: "#{project_tab_class} separate-item"}) do
= link_to edit_namespace_project_path(@project.namespace, @project), title: 'Settings', class: "stat-tab tab no-highlight" do = link_to edit_project_path(@project), title: 'Settings', class: "stat-tab tab no-highlight" do
%i.fa.fa-cogs %i.fa.fa-cogs
%span %span
Settings Settings
!!! 5 !!! 5
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head", title: @title = render "layouts/head", title: @title
%body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} %body{class: "#{app_theme} application", :'data-page' => body_data_page}
= render "layouts/broadcast" = render "layouts/broadcast"
= render "layouts/head_panel", title: defined?(@title_url) ? link_to(@title, @title_url) : @title = render "layouts/head_panel", title: defined?(@title_url) ? link_to(@title, @title_url) : @title
.container.navless-container .container.navless-container
......
!!! 5 !!! 5
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head", title: "Profile" = render "layouts/head", title: "Profile"
%body{class: "#{app_theme} #{theme_type} profile", :'data-page' => body_data_page} %body{class: "#{app_theme} profile", :'data-page' => body_data_page}
= render "layouts/head_panel", title: link_to("Profile", profile_path) = render "layouts/head_panel", title: link_to("Profile", profile_path)
= render 'layouts/page', sidebar: 'layouts/nav/profile' = render 'layouts/page', sidebar: 'layouts/nav/profile'
!!! 5 !!! 5
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head", title: @project.name_with_namespace = render "layouts/head", title: @project.name_with_namespace
%body{class: "#{app_theme} #{theme_type} project", :'data-page' => body_data_page, :'data-project-id' => @project.id } %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id }
= render "layouts/head_panel", title: project_title(@project) = render "layouts/head_panel", title: project_title(@project)
= render "layouts/init_auto_complete" = render "layouts/init_auto_complete"
- @project_settings_nav = true - @project_settings_nav = true
......
!!! 5 !!! 5
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head", title: project_head_title = render "layouts/head", title: project_head_title
%body{class: "#{app_theme} #{theme_type} project", :'data-page' => body_data_page, :'data-project-id' => @project.id } %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id }
= render "layouts/head_panel", title: project_title(@project) = render "layouts/head_panel", title: project_title(@project)
= render "layouts/init_auto_complete" = render "layouts/init_auto_complete"
= render 'layouts/page', sidebar: 'layouts/nav/project' = render 'layouts/page', sidebar: 'layouts/nav/project'
!!! 5 !!! 5
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head", title: group_head_title = render "layouts/head", title: group_head_title
%body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} %body{class: "#{app_theme} application", :'data-page' => body_data_page}
= render "layouts/public_head_panel", title: link_to(@group.name, group_path(@group)) = render "layouts/public_head_panel", title: link_to(@group.name, group_path(@group))
= render 'layouts/page', sidebar: 'layouts/nav/group' = render 'layouts/page', sidebar: 'layouts/nav/group'
!!! 5 !!! 5
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head", title: @project.name_with_namespace = render "layouts/head", title: @project.name_with_namespace
%body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} %body{class: "#{app_theme} application", :'data-page' => body_data_page}
= render "layouts/public_head_panel", title: project_title(@project) = render "layouts/public_head_panel", title: project_title(@project)
= render 'layouts/page', sidebar: 'layouts/nav/project' = render 'layouts/page', sidebar: 'layouts/nav/project'
!!! 5 !!! 5
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head", title: @title = render "layouts/head", title: @title
%body{class: "#{app_theme} #{theme_type} application", :'data-page' => body_data_page} %body{class: "#{app_theme} application", :'data-page' => body_data_page}
= render "layouts/public_head_panel", title: defined?(@title_url) ? link_to(@title, @title_url) : @title = render "layouts/public_head_panel", title: defined?(@title_url) ? link_to(@title, @title_url) : @title
= render 'layouts/page' = render 'layouts/page'
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment