Commit 66ebf8d83f894ed361031eb9ede5a8b829fefd36
Exists in
master
and in
4 other branches
Merge remote-tracking branch 'github/master'
Showing
190 changed files
with
4007 additions
and
974 deletions
Show diff stats
Too many changes.
To preserve performance only 100 of 190 files displayed.
Procfile
1.55 KB
1.5 KB
| ... | ... | @@ -0,0 +1,128 @@ |
| 1 | +class ImageFile | |
| 2 | + | |
| 3 | + # Width where images must fits in, for 2-up this gets divided by 2 | |
| 4 | + @availWidth = 900 | |
| 5 | + @viewModes = ['two-up', 'swipe'] | |
| 6 | + | |
| 7 | + constructor: (@file) -> | |
| 8 | + # Determine if old and new file has same dimensions, if not show 'two-up' view | |
| 9 | + this.requestImageInfo $('.two-up.view .frame.deleted img', @file), (deletedWidth, deletedHeight) => | |
| 10 | + this.requestImageInfo $('.two-up.view .frame.added img', @file), (width, height) => | |
| 11 | + if width == deletedWidth && height == deletedHeight | |
| 12 | + this.initViewModes() | |
| 13 | + else | |
| 14 | + this.initView('two-up') | |
| 15 | + | |
| 16 | + initViewModes: -> | |
| 17 | + viewMode = ImageFile.viewModes[0] | |
| 18 | + | |
| 19 | + $('.view-modes', @file).removeClass 'hide' | |
| 20 | + $('.view-modes-menu', @file).on 'click', 'li', (event) => | |
| 21 | + unless $(event.currentTarget).hasClass('active') | |
| 22 | + this.activateViewMode(event.currentTarget.className) | |
| 23 | + | |
| 24 | + this.activateViewMode(viewMode) | |
| 25 | + | |
| 26 | + activateViewMode: (viewMode) -> | |
| 27 | + $('.view-modes-menu li', @file) | |
| 28 | + .removeClass('active') | |
| 29 | + .filter(".#{viewMode}").addClass 'active' | |
| 30 | + $(".view:visible:not(.#{viewMode})", @file).fadeOut 200, => | |
| 31 | + $(".view.#{viewMode}", @file).fadeIn(200) | |
| 32 | + this.initView viewMode | |
| 33 | + | |
| 34 | + initView: (viewMode) -> | |
| 35 | + this.views[viewMode].call(this) | |
| 36 | + | |
| 37 | + prepareFrames = (view) -> | |
| 38 | + maxWidth = 0 | |
| 39 | + maxHeight = 0 | |
| 40 | + $('.frame', view).each (index, frame) => | |
| 41 | + width = $(frame).width() | |
| 42 | + height = $(frame).height() | |
| 43 | + maxWidth = if width > maxWidth then width else maxWidth | |
| 44 | + maxHeight = if height > maxHeight then height else maxHeight | |
| 45 | + .css | |
| 46 | + width: maxWidth | |
| 47 | + height: maxHeight | |
| 48 | + | |
| 49 | + [maxWidth, maxHeight] | |
| 50 | + | |
| 51 | + views: | |
| 52 | + 'two-up': -> | |
| 53 | + $('.two-up.view .wrap', @file).each (index, wrap) => | |
| 54 | + $('img', wrap).each -> | |
| 55 | + currentWidth = $(this).width() | |
| 56 | + if currentWidth > ImageFile.availWidth / 2 | |
| 57 | + $(this).width ImageFile.availWidth / 2 | |
| 58 | + | |
| 59 | + this.requestImageInfo $('img', wrap), (width, height) -> | |
| 60 | + $('.image-info .meta-width', wrap).text "#{width}px" | |
| 61 | + $('.image-info .meta-height', wrap).text "#{height}px" | |
| 62 | + $('.image-info', wrap).removeClass('hide') | |
| 63 | + | |
| 64 | + 'swipe': -> | |
| 65 | + maxWidth = 0 | |
| 66 | + maxHeight = 0 | |
| 67 | + | |
| 68 | + $('.swipe.view', @file).each (index, view) => | |
| 69 | + | |
| 70 | + [maxWidth, maxHeight] = prepareFrames(view) | |
| 71 | + | |
| 72 | + $('.swipe-frame', view).css | |
| 73 | + width: maxWidth + 16 | |
| 74 | + height: maxHeight + 28 | |
| 75 | + | |
| 76 | + $('.swipe-wrap', view).css | |
| 77 | + width: maxWidth + 1 | |
| 78 | + height: maxHeight + 2 | |
| 79 | + | |
| 80 | + $('.swipe-bar', view).css | |
| 81 | + left: 0 | |
| 82 | + .draggable | |
| 83 | + axis: 'x' | |
| 84 | + containment: 'parent' | |
| 85 | + drag: (event) -> | |
| 86 | + $('.swipe-wrap', view).width (maxWidth + 1) - $(this).position().left | |
| 87 | + stop: (event) -> | |
| 88 | + $('.swipe-wrap', view).width (maxWidth + 1) - $(this).position().left | |
| 89 | + | |
| 90 | + 'onion-skin': -> | |
| 91 | + maxWidth = 0 | |
| 92 | + maxHeight = 0 | |
| 93 | + | |
| 94 | + dragTrackWidth = $('.drag-track', @file).width() - $('.dragger', @file).width() | |
| 95 | + | |
| 96 | + $('.onion-skin.view', @file).each (index, view) => | |
| 97 | + | |
| 98 | + [maxWidth, maxHeight] = prepareFrames(view) | |
| 99 | + | |
| 100 | + $('.onion-skin-frame', view).css | |
| 101 | + width: maxWidth + 16 | |
| 102 | + height: maxHeight + 28 | |
| 103 | + | |
| 104 | + $('.swipe-wrap', view).css | |
| 105 | + width: maxWidth + 1 | |
| 106 | + height: maxHeight + 2 | |
| 107 | + | |
| 108 | + $('.dragger', view).css | |
| 109 | + left: dragTrackWidth | |
| 110 | + .draggable | |
| 111 | + axis: 'x' | |
| 112 | + containment: 'parent' | |
| 113 | + drag: (event) -> | |
| 114 | + $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth) | |
| 115 | + stop: (event) -> | |
| 116 | + $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth) | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + requestImageInfo: (img, callback) -> | |
| 121 | + domImg = img.get(0) | |
| 122 | + if domImg.complete | |
| 123 | + callback.call(this, domImg.naturalWidth, domImg.naturalHeight) | |
| 124 | + else | |
| 125 | + img.on 'load', => | |
| 126 | + callback.call(this, domImg.naturalWidth, domImg.naturalHeight) | |
| 127 | + | |
| 128 | +this.ImageFile = ImageFile | |
| 0 | 129 | \ No newline at end of file | ... | ... |
app/assets/javascripts/commits.js
| ... | ... | @@ -1,59 +0,0 @@ |
| 1 | -var CommitsList = { | |
| 2 | - ref:null, | |
| 3 | - limit:0, | |
| 4 | - offset:0, | |
| 5 | - disable:false, | |
| 6 | - | |
| 7 | - init: | |
| 8 | - function(ref, limit) { | |
| 9 | - $(".day-commits-table li.commit").live('click', function(e){ | |
| 10 | - if(e.target.nodeName != "A") { | |
| 11 | - location.href = $(this).attr("url"); | |
| 12 | - e.stopPropagation(); | |
| 13 | - return false; | |
| 14 | - } | |
| 15 | - }); | |
| 16 | - | |
| 17 | - this.ref=ref; | |
| 18 | - this.limit=limit; | |
| 19 | - this.offset=limit; | |
| 20 | - this.initLoadMore(); | |
| 21 | - $('.loading').show(); | |
| 22 | - }, | |
| 23 | - | |
| 24 | - getOld: | |
| 25 | - function() { | |
| 26 | - $('.loading').show(); | |
| 27 | - $.ajax({ | |
| 28 | - type: "GET", | |
| 29 | - url: location.href, | |
| 30 | - data: "limit=" + this.limit + "&offset=" + this.offset + "&ref=" + this.ref, | |
| 31 | - complete: function(){ $('.loading').hide()}, | |
| 32 | - dataType: "script"}); | |
| 33 | - }, | |
| 34 | - | |
| 35 | - append: | |
| 36 | - function(count, html) { | |
| 37 | - $("#commits_list").append(html); | |
| 38 | - if(count > 0) { | |
| 39 | - this.offset += count; | |
| 40 | - } else { | |
| 41 | - this.disable = true; | |
| 42 | - } | |
| 43 | - }, | |
| 44 | - | |
| 45 | - initLoadMore: | |
| 46 | - function() { | |
| 47 | - $(document).endlessScroll({ | |
| 48 | - bottomPixels: 400, | |
| 49 | - fireDelay: 1000, | |
| 50 | - fireOnce:true, | |
| 51 | - ceaseFire: function() { | |
| 52 | - return CommitsList.disable; | |
| 53 | - }, | |
| 54 | - callback: function(i) { | |
| 55 | - CommitsList.getOld(); | |
| 56 | - } | |
| 57 | - }); | |
| 58 | - } | |
| 59 | -} |
| ... | ... | @@ -0,0 +1,54 @@ |
| 1 | +class CommitsList | |
| 2 | + @data = | |
| 3 | + ref: null | |
| 4 | + limit: 0 | |
| 5 | + offset: 0 | |
| 6 | + @disable = false | |
| 7 | + | |
| 8 | + @showProgress: -> | |
| 9 | + $('.loading').show() | |
| 10 | + | |
| 11 | + @hideProgress: -> | |
| 12 | + $('.loading').hide() | |
| 13 | + | |
| 14 | + @init: (ref, limit) -> | |
| 15 | + $(".day-commits-table li.commit").live 'click', (event) -> | |
| 16 | + if event.target.nodeName != "A" | |
| 17 | + location.href = $(this).attr("url") | |
| 18 | + e.stopPropagation() | |
| 19 | + return false | |
| 20 | + | |
| 21 | + @data.ref = ref | |
| 22 | + @data.limit = limit | |
| 23 | + @data.offset = limit | |
| 24 | + | |
| 25 | + this.initLoadMore() | |
| 26 | + this.showProgress(); | |
| 27 | + | |
| 28 | + @getOld: -> | |
| 29 | + this.showProgress() | |
| 30 | + $.ajax | |
| 31 | + type: "GET" | |
| 32 | + url: location.href | |
| 33 | + data: @data | |
| 34 | + complete: this.hideProgress | |
| 35 | + dataType: "script" | |
| 36 | + | |
| 37 | + @append: (count, html) -> | |
| 38 | + $("#commits-list").append(html) | |
| 39 | + if count > 0 | |
| 40 | + @data.offset += count | |
| 41 | + else | |
| 42 | + @disable = true | |
| 43 | + | |
| 44 | + @initLoadMore: -> | |
| 45 | + $(document).endlessScroll | |
| 46 | + bottomPixels: 400 | |
| 47 | + fireDelay: 1000 | |
| 48 | + fireOnce: true | |
| 49 | + ceaseFire: => | |
| 50 | + @disable | |
| 51 | + callback: => | |
| 52 | + this.getOld() | |
| 53 | + | |
| 54 | +this.CommitsList = CommitsList | |
| 0 | 55 | \ No newline at end of file | ... | ... |
app/assets/javascripts/dashboard.js.coffee
| ... | ... | @@ -4,11 +4,11 @@ window.dashboardPage = -> |
| 4 | 4 | event.preventDefault() |
| 5 | 5 | toggleFilter $(this) |
| 6 | 6 | reloadActivities() |
| 7 | - | |
| 7 | + | |
| 8 | 8 | reloadActivities = -> |
| 9 | 9 | $(".content_list").html '' |
| 10 | 10 | Pager.init 20, true |
| 11 | - | |
| 11 | + | |
| 12 | 12 | toggleFilter = (sender) -> |
| 13 | 13 | sender.parent().toggleClass "inactive" |
| 14 | 14 | event_filters = $.cookie("event_filter") |
| ... | ... | @@ -17,11 +17,11 @@ toggleFilter = (sender) -> |
| 17 | 17 | event_filters = event_filters.split(",") |
| 18 | 18 | else |
| 19 | 19 | event_filters = new Array() |
| 20 | - | |
| 20 | + | |
| 21 | 21 | index = event_filters.indexOf(filter) |
| 22 | 22 | if index is -1 |
| 23 | 23 | event_filters.push filter |
| 24 | 24 | else |
| 25 | 25 | event_filters.splice index, 1 |
| 26 | - | |
| 26 | + | |
| 27 | 27 | $.cookie "event_filter", event_filters.join(",") | ... | ... |
app/assets/javascripts/merge_requests.js.coffee
| 1 | 1 | # |
| 2 | 2 | # * Filter merge requests |
| 3 | -# | |
| 3 | +# | |
| 4 | 4 | @merge_requestsPage = -> |
| 5 | 5 | $('#assignee_id').chosen() |
| 6 | 6 | $('#milestone_id').chosen() |
| ... | ... | @@ -8,16 +8,16 @@ |
| 8 | 8 | $(this).closest('form').submit() |
| 9 | 9 | |
| 10 | 10 | class MergeRequest |
| 11 | - | |
| 11 | + | |
| 12 | 12 | constructor: (@opts) -> |
| 13 | 13 | this.$el = $('.merge-request') |
| 14 | 14 | @diffs_loaded = false |
| 15 | 15 | @commits_loaded = false |
| 16 | - | |
| 16 | + | |
| 17 | 17 | this.activateTab(@opts.action) |
| 18 | - | |
| 18 | + | |
| 19 | 19 | this.bindEvents() |
| 20 | - | |
| 20 | + | |
| 21 | 21 | this.initMergeWidget() |
| 22 | 22 | this.$('.show-all-commits').on 'click', => |
| 23 | 23 | this.showAllCommits() |
| ... | ... | @@ -28,7 +28,7 @@ class MergeRequest |
| 28 | 28 | |
| 29 | 29 | initMergeWidget: -> |
| 30 | 30 | this.showState( @opts.current_state ) |
| 31 | - | |
| 31 | + | |
| 32 | 32 | if this.$('.automerge_widget').length and @opts.check_enable |
| 33 | 33 | $.get @opts.url_to_automerge_check, (data) => |
| 34 | 34 | this.showState( data.state ) |
| ... | ... | @@ -42,12 +42,12 @@ class MergeRequest |
| 42 | 42 | bindEvents: -> |
| 43 | 43 | this.$('.nav-tabs').on 'click', 'a', (event) => |
| 44 | 44 | a = $(event.currentTarget) |
| 45 | - | |
| 45 | + | |
| 46 | 46 | href = a.attr('href') |
| 47 | 47 | History.replaceState {path: href}, document.title, href |
| 48 | - | |
| 48 | + | |
| 49 | 49 | event.preventDefault() |
| 50 | - | |
| 50 | + | |
| 51 | 51 | this.$('.nav-tabs').on 'click', 'li', (event) => |
| 52 | 52 | this.activateTab($(event.currentTarget).data('action')) |
| 53 | 53 | ... | ... |
app/assets/javascripts/pager.js
| ... | ... | @@ -1,56 +0,0 @@ |
| 1 | -var Pager = { | |
| 2 | - limit:0, | |
| 3 | - offset:0, | |
| 4 | - disable:false, | |
| 5 | - | |
| 6 | - init: | |
| 7 | - function(limit, preload) { | |
| 8 | - this.limit=limit; | |
| 9 | - | |
| 10 | - if(preload) { | |
| 11 | - this.offset = 0; | |
| 12 | - this.getOld(); | |
| 13 | - } else { | |
| 14 | - this.offset = limit; | |
| 15 | - } | |
| 16 | - | |
| 17 | - this.initLoadMore(); | |
| 18 | - }, | |
| 19 | - | |
| 20 | - getOld: | |
| 21 | - function() { | |
| 22 | - $('.loading').show(); | |
| 23 | - $.ajax({ | |
| 24 | - type: "GET", | |
| 25 | - url: location.href, | |
| 26 | - data: "limit=" + this.limit + "&offset=" + this.offset, | |
| 27 | - complete: function(){ $('.loading').hide()}, | |
| 28 | - dataType: "script"}); | |
| 29 | - }, | |
| 30 | - | |
| 31 | - append: | |
| 32 | - function(count, html) { | |
| 33 | - $(".content_list").append(html); | |
| 34 | - if(count > 0) { | |
| 35 | - this.offset += count; | |
| 36 | - } else { | |
| 37 | - this.disable = true; | |
| 38 | - } | |
| 39 | - }, | |
| 40 | - | |
| 41 | - initLoadMore: | |
| 42 | - function() { | |
| 43 | - $(document).endlessScroll({ | |
| 44 | - bottomPixels: 400, | |
| 45 | - fireDelay: 1000, | |
| 46 | - fireOnce:true, | |
| 47 | - ceaseFire: function() { | |
| 48 | - return Pager.disable; | |
| 49 | - }, | |
| 50 | - callback: function(i) { | |
| 51 | - $('.loading').show(); | |
| 52 | - Pager.getOld(); | |
| 53 | - } | |
| 54 | - }); | |
| 55 | - } | |
| 56 | -} |
| ... | ... | @@ -0,0 +1,42 @@ |
| 1 | +@Pager = | |
| 2 | + limit: 0 | |
| 3 | + offset: 0 | |
| 4 | + disable: false | |
| 5 | + init: (limit, preload) -> | |
| 6 | + @limit = limit | |
| 7 | + if preload | |
| 8 | + @offset = 0 | |
| 9 | + @getOld() | |
| 10 | + else | |
| 11 | + @offset = limit | |
| 12 | + @initLoadMore() | |
| 13 | + | |
| 14 | + getOld: -> | |
| 15 | + $(".loading").show() | |
| 16 | + $.ajax | |
| 17 | + type: "GET" | |
| 18 | + url: location.href | |
| 19 | + data: "limit=" + @limit + "&offset=" + @offset | |
| 20 | + complete: -> | |
| 21 | + $(".loading").hide() | |
| 22 | + | |
| 23 | + dataType: "script" | |
| 24 | + | |
| 25 | + append: (count, html) -> | |
| 26 | + $(".content_list").append html | |
| 27 | + if count > 0 | |
| 28 | + @offset += count | |
| 29 | + else | |
| 30 | + @disable = true | |
| 31 | + | |
| 32 | + initLoadMore: -> | |
| 33 | + $(document).endlessScroll | |
| 34 | + bottomPixels: 400 | |
| 35 | + fireDelay: 1000 | |
| 36 | + fireOnce: true | |
| 37 | + ceaseFire: -> | |
| 38 | + Pager.disable | |
| 39 | + | |
| 40 | + callback: (i) -> | |
| 41 | + $(".loading").show() | |
| 42 | + Pager.getOld() | ... | ... |
app/assets/stylesheets/common.scss
| 1 | 1 | html { |
| 2 | - overflow-y: scroll; | |
| 2 | + overflow-y: scroll; | |
| 3 | 3 | } |
| 4 | 4 | |
| 5 | 5 | /** LAYOUT **/ |
| ... | ... | @@ -277,8 +277,20 @@ p.time { |
| 277 | 277 | } |
| 278 | 278 | } |
| 279 | 279 | |
| 280 | +.search-holder { | |
| 281 | + label, input { | |
| 282 | + height: 30px; | |
| 283 | + padding: 0; | |
| 284 | + font-size: 14px; | |
| 285 | + } | |
| 286 | + label { | |
| 287 | + line-height: 30px; | |
| 288 | + color: #666; | |
| 289 | + } | |
| 290 | +} | |
| 291 | + | |
| 280 | 292 | .highlight_word { |
| 281 | - background: #EEDC94; | |
| 293 | + border-bottom: 2px solid #F90; | |
| 282 | 294 | } |
| 283 | 295 | |
| 284 | 296 | .status_info { |
| ... | ... | @@ -326,7 +338,7 @@ li.note { |
| 326 | 338 | li { |
| 327 | 339 | border-bottom:none !important; |
| 328 | 340 | } |
| 329 | - .file { | |
| 341 | + .attachment { | |
| 330 | 342 | padding-left: 20px; |
| 331 | 343 | background:url("icon-attachment.png") no-repeat left center; |
| 332 | 344 | } | ... | ... |
app/assets/stylesheets/gitlab_bootstrap/files.scss
| ... | ... | @@ -135,7 +135,7 @@ |
| 135 | 135 | pre { |
| 136 | 136 | border: none; |
| 137 | 137 | border-radius: 0; |
| 138 | - font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; | |
| 138 | + font-family: $monospace_font; | |
| 139 | 139 | font-size: 12px !important; |
| 140 | 140 | line-height: 16px !important; |
| 141 | 141 | margin: 0; | ... | ... |
app/assets/stylesheets/gitlab_bootstrap/fonts.scss
app/assets/stylesheets/gitlab_bootstrap/typography.scss
| ... | ... | @@ -21,7 +21,7 @@ h6 { |
| 21 | 21 | |
| 22 | 22 | /** CODE **/ |
| 23 | 23 | pre { |
| 24 | - font-family:'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; | |
| 24 | + font-family: $monospace_font; | |
| 25 | 25 | |
| 26 | 26 | &.dark { |
| 27 | 27 | background: #333; |
| ... | ... | @@ -79,7 +79,7 @@ a:focus { |
| 79 | 79 | } |
| 80 | 80 | |
| 81 | 81 | .monospace { |
| 82 | - font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; | |
| 82 | + font-family: $monospace_font; | |
| 83 | 83 | } |
| 84 | 84 | |
| 85 | 85 | /** | ... | ... |
app/assets/stylesheets/gitlab_bootstrap/variables.scss
app/assets/stylesheets/sections/commits.scss
| 1 | 1 | /** |
| 2 | - * | |
| 3 | - * COMMIT SHOw | |
| 4 | - * | |
| 2 | + * Commit file | |
| 5 | 3 | */ |
| 6 | 4 | .commit-committer-link, |
| 7 | 5 | .commit-author-link { |
| ... | ... | @@ -12,11 +10,11 @@ |
| 12 | 10 | } |
| 13 | 11 | } |
| 14 | 12 | |
| 15 | -.diff_file { | |
| 13 | +.file { | |
| 16 | 14 | border: 1px solid #CCC; |
| 17 | 15 | margin-bottom: 1em; |
| 18 | 16 | |
| 19 | - .diff_file_header { | |
| 17 | + .header { | |
| 20 | 18 | @extend .clearfix; |
| 21 | 19 | padding: 5px 5px 5px 10px; |
| 22 | 20 | color: #555; |
| ... | ... | @@ -28,32 +26,35 @@ |
| 28 | 26 | background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); |
| 29 | 27 | background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); |
| 30 | 28 | |
| 29 | + a{ | |
| 30 | + color: $style_color; | |
| 31 | + } | |
| 32 | + | |
| 31 | 33 | > span { |
| 32 | - font-family: $monospace; | |
| 34 | + font-family: $monospace_font; | |
| 33 | 35 | font-size: 14px; |
| 34 | 36 | line-height: 30px; |
| 35 | 37 | } |
| 36 | 38 | |
| 37 | - a.view-commit{ | |
| 39 | + a.view-file{ | |
| 38 | 40 | font-weight: bold; |
| 39 | 41 | } |
| 40 | 42 | |
| 41 | 43 | .commit-short-id{ |
| 42 | - font-family: $monospace; | |
| 44 | + font-family: $monospace_font; | |
| 43 | 45 | font-size: smaller; |
| 44 | 46 | } |
| 45 | 47 | |
| 46 | 48 | .file-mode{ |
| 47 | - font-family: $monospace; | |
| 49 | + font-family: $monospace_font; | |
| 48 | 50 | } |
| 49 | 51 | } |
| 50 | - .diff_file_content { | |
| 52 | + .content { | |
| 51 | 53 | overflow: auto; |
| 52 | 54 | overflow-y: hidden; |
| 53 | - background: #fff; | |
| 55 | + background: #FFF; | |
| 54 | 56 | color: #333; |
| 55 | 57 | font-size: 12px; |
| 56 | - font-family: $monospace; | |
| 57 | 58 | .old{ |
| 58 | 59 | span.idiff{ |
| 59 | 60 | background-color: #FAA; |
| ... | ... | @@ -66,114 +67,274 @@ |
| 66 | 67 | } |
| 67 | 68 | |
| 68 | 69 | table { |
| 70 | + font-family: $monospace_font; | |
| 71 | + border: none; | |
| 72 | + margin: 0px; | |
| 73 | + padding: 0px; | |
| 69 | 74 | td { |
| 70 | 75 | line-height: 18px; |
| 76 | + font-size: 12px; | |
| 77 | + } | |
| 78 | + } | |
| 79 | + .old_line, .new_line { | |
| 80 | + margin: 0px; | |
| 81 | + padding: 0px; | |
| 82 | + border: none; | |
| 83 | + background: #EEE; | |
| 84 | + color: #666; | |
| 85 | + padding: 0px 5px; | |
| 86 | + border-right: 1px solid #ccc; | |
| 87 | + text-align: right; | |
| 88 | + min-width: 35px; | |
| 89 | + max-width: 35px; | |
| 90 | + width: 35px; | |
| 91 | + @include user-select(none); | |
| 92 | + a { | |
| 93 | + float: left; | |
| 94 | + width: 35px; | |
| 95 | + font-weight: normal; | |
| 96 | + color: #666; | |
| 97 | + &:hover { | |
| 98 | + text-decoration: underline; | |
| 99 | + } | |
| 100 | + } | |
| 101 | + } | |
| 102 | + .line_content { | |
| 103 | + white-space: pre; | |
| 104 | + height: 14px; | |
| 105 | + margin: 0px; | |
| 106 | + padding: 0px; | |
| 107 | + border: none; | |
| 108 | + &.new { | |
| 109 | + background: #CFD; | |
| 110 | + } | |
| 111 | + &.old { | |
| 112 | + background: #FDD; | |
| 113 | + } | |
| 114 | + &.matched { | |
| 115 | + color: #ccc; | |
| 116 | + background: #fafafa; | |
| 71 | 117 | } |
| 72 | 118 | } |
| 73 | 119 | } |
| 74 | - .diff_file_content_image { | |
| 75 | - background: #eee; | |
| 120 | + .image { | |
| 121 | + background: #ddd; | |
| 76 | 122 | text-align: center; |
| 77 | - .image { | |
| 123 | + padding: 30px; | |
| 124 | + .wrap{ | |
| 78 | 125 | display: inline-block; |
| 79 | - margin: 50px; | |
| 80 | - max-width: 400px; | |
| 81 | - | |
| 126 | + } | |
| 127 | + | |
| 128 | + .frame { | |
| 129 | + display: inline-block; | |
| 130 | + background-color: #fff; | |
| 131 | + line-height: 0; | |
| 82 | 132 | img{ |
| 133 | + border: 1px solid #FFF; | |
| 83 | 134 | background: url('trans_bg.gif'); |
| 84 | 135 | } |
| 136 | + &.deleted { | |
| 137 | + border: 1px solid $deleted; | |
| 138 | + } | |
| 85 | 139 | |
| 86 | - &.diff_removed { | |
| 87 | - img{ | |
| 88 | - border: 1px solid #C00; | |
| 89 | - } | |
| 140 | + &.added { | |
| 141 | + border: 1px solid $added; | |
| 90 | 142 | } |
| 143 | + } | |
| 144 | + .image-info{ | |
| 145 | + font-size: 12px; | |
| 146 | + margin: 5px 0 0 0; | |
| 147 | + color: grey; | |
| 148 | + } | |
| 91 | 149 | |
| 92 | - &.diff_added { | |
| 93 | - img{ | |
| 94 | - border: 1px solid #0C0; | |
| 150 | + .view.swipe{ | |
| 151 | + position: relative; | |
| 152 | + | |
| 153 | + .swipe-frame{ | |
| 154 | + display: block; | |
| 155 | + margin: auto; | |
| 156 | + position: relative; | |
| 157 | + } | |
| 158 | + .swipe-wrap{ | |
| 159 | + overflow: hidden; | |
| 160 | + border-left: 1px solid #999; | |
| 161 | + position: absolute; | |
| 162 | + display: block; | |
| 163 | + top: 13px; | |
| 164 | + right: 7px; | |
| 165 | + } | |
| 166 | + .frame{ | |
| 167 | + top: 0; | |
| 168 | + right: 0; | |
| 169 | + position: absolute; | |
| 170 | + &.deleted{ | |
| 171 | + margin: 0; | |
| 172 | + display: block; | |
| 173 | + top: 13px; | |
| 174 | + right: 7px; | |
| 95 | 175 | } |
| 96 | 176 | } |
| 97 | - | |
| 98 | - .image-info{ | |
| 99 | - margin: 5px 0 0 0; | |
| 177 | + .swipe-bar{ | |
| 178 | + display: block; | |
| 179 | + height: 100%; | |
| 180 | + width: 15px; | |
| 181 | + z-index: 100; | |
| 182 | + position: absolute; | |
| 183 | + cursor: pointer; | |
| 184 | + &:hover{ | |
| 185 | + .top-handle{ | |
| 186 | + background-position: -15px 3px; | |
| 187 | + } | |
| 188 | + .bottom-handle{ | |
| 189 | + background-position: -15px -11px; | |
| 190 | + } | |
| 191 | + }; | |
| 192 | + .top-handle{ | |
| 193 | + display: block; | |
| 194 | + height: 14px; | |
| 195 | + width: 15px; | |
| 196 | + position: absolute; | |
| 197 | + top: 0px; | |
| 198 | + background: url('swipemode_sprites.gif') 0 3px no-repeat; | |
| 199 | + } | |
| 200 | + .bottom-handle{ | |
| 201 | + display: block; | |
| 202 | + height: 14px; | |
| 203 | + width: 15px; | |
| 204 | + position: absolute; | |
| 205 | + bottom: 0px; | |
| 206 | + background: url('swipemode_sprites.gif') 0 -11px no-repeat; | |
| 207 | + } | |
| 100 | 208 | } |
| 101 | - } | |
| 102 | - | |
| 103 | - &.img_compared { | |
| 104 | - .image { | |
| 105 | - max-width: 300px; | |
| 209 | + } //.view.swipe | |
| 210 | + .view.onion-skin{ | |
| 211 | + .onion-skin-frame{ | |
| 212 | + display: block; | |
| 213 | + margin: auto; | |
| 214 | + position: relative; | |
| 106 | 215 | } |
| 107 | - } | |
| 216 | + .frame.added, .frame.deleted { | |
| 217 | + position: absolute; | |
| 218 | + display: block; | |
| 219 | + top: 0px; | |
| 220 | + left: 0px; | |
| 221 | + } | |
| 222 | + .controls{ | |
| 223 | + display: block; | |
| 224 | + height: 14px; | |
| 225 | + width: 300px; | |
| 226 | + z-index: 100; | |
| 227 | + position: absolute; | |
| 228 | + bottom: 0px; | |
| 229 | + left: 50%; | |
| 230 | + margin-left: -150px; | |
| 231 | + | |
| 232 | + .drag-track{ | |
| 233 | + display: block; | |
| 234 | + position: absolute; | |
| 235 | + left: 12px; | |
| 236 | + height: 10px; | |
| 237 | + width: 276px; | |
| 238 | + background: url('onion_skin_sprites.gif') -4px -20px repeat-x; | |
| 239 | + } | |
| 240 | + | |
| 241 | + .dragger { | |
| 242 | + display: block; | |
| 243 | + position: absolute; | |
| 244 | + left: 0px; | |
| 245 | + top: 0px; | |
| 246 | + height: 14px; | |
| 247 | + width: 14px; | |
| 248 | + background: url('onion_skin_sprites.gif') 0px -34px repeat-x; | |
| 249 | + cursor: pointer; | |
| 250 | + } | |
| 251 | + | |
| 252 | + .transparent { | |
| 253 | + display: block; | |
| 254 | + position: absolute; | |
| 255 | + top: 2px; | |
| 256 | + right: 0px; | |
| 257 | + height: 10px; | |
| 258 | + width: 10px; | |
| 259 | + background: url('onion_skin_sprites.gif') -2px 0px no-repeat; | |
| 260 | + } | |
| 261 | + | |
| 262 | + .opaque { | |
| 263 | + display: block; | |
| 264 | + position: absolute; | |
| 265 | + top: 2px; | |
| 266 | + left: 0px; | |
| 267 | + height: 10px; | |
| 268 | + width: 10px; | |
| 269 | + background: url('onion_skin_sprites.gif') -2px -10px no-repeat; | |
| 270 | + } | |
| 271 | + } | |
| 272 | + } //.view.onion-skin | |
| 108 | 273 | } |
| 109 | -} | |
| 274 | + .view-modes{ | |
| 110 | 275 | |
| 111 | -.diff_file_content{ | |
| 112 | - table { | |
| 113 | - border: none; | |
| 114 | - margin: 0px; | |
| 115 | - padding: 0px; | |
| 116 | - tr { | |
| 117 | - td { | |
| 118 | - font-size: 12px; | |
| 119 | - } | |
| 276 | + padding: 10px; | |
| 277 | + text-align: center; | |
| 278 | + | |
| 279 | + background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf)); | |
| 280 | + background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf); | |
| 281 | + background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); | |
| 282 | + background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); | |
| 283 | + | |
| 284 | + ul, li{ | |
| 285 | + list-style: none; | |
| 286 | + margin: 0; | |
| 287 | + padding: 0; | |
| 288 | + display: inline-block; | |
| 120 | 289 | } |
| 121 | - } | |
| 122 | - .new_line, | |
| 123 | - .old_line, | |
| 124 | - .notes_line { | |
| 125 | - margin:0px; | |
| 126 | - padding:0px; | |
| 127 | - border:none; | |
| 128 | - background:#EEE; | |
| 129 | - color:#666; | |
| 130 | - padding: 0px 5px; | |
| 131 | - border-right: 1px solid #ccc; | |
| 132 | - text-align: right; | |
| 133 | - min-width: 35px; | |
| 134 | - max-width: 35px; | |
| 135 | - width: 35px; | |
| 136 | - moz-user-select: none; | |
| 137 | - -khtml-user-select: none; | |
| 138 | - user-select: none; | |
| 139 | - | |
| 140 | - a { | |
| 141 | - float: left; | |
| 142 | - width: 35px; | |
| 143 | - font-weight: normal; | |
| 144 | - color: #666; | |
| 145 | - &:hover { | |
| 290 | + | |
| 291 | + li{ | |
| 292 | + color: grey; | |
| 293 | + border-left: 1px solid #c1c1c1; | |
| 294 | + padding: 0 12px 0 16px; | |
| 295 | + cursor: pointer; | |
| 296 | + &:first-child{ | |
| 297 | + border-left: none; | |
| 298 | + } | |
| 299 | + &:hover{ | |
| 146 | 300 | text-decoration: underline; |
| 147 | 301 | } |
| 148 | - } | |
| 149 | - } | |
| 150 | - .line_content { | |
| 151 | - white-space: pre; | |
| 152 | - height: 14px; | |
| 153 | - margin: 0px; | |
| 154 | - padding: 0px; | |
| 155 | - border: none; | |
| 156 | - &.new { | |
| 157 | - background: #CFD; | |
| 158 | - } | |
| 159 | - &.old { | |
| 160 | - background: #FDD; | |
| 161 | - } | |
| 162 | - &.matched { | |
| 163 | - color: #ccc; | |
| 164 | - background: #fafafa; | |
| 302 | + &.active{ | |
| 303 | + &:hover{ | |
| 304 | + text-decoration: none; | |
| 305 | + } | |
| 306 | + cursor: default; | |
| 307 | + color: #333; | |
| 308 | + } | |
| 309 | + &.disabled{ | |
| 310 | + display: none; | |
| 311 | + } | |
| 165 | 312 | } |
| 166 | 313 | } |
| 167 | 314 | } |
| 168 | 315 | |
| 169 | 316 | /** COMMIT BLOCK **/ |
| 170 | -.commit-title{display: block;} | |
| 171 | -.commit-title{margin-bottom: 10px} | |
| 172 | -.commit-author, .commit-committer{display: block;color: #999; font-weight: normal; font-style: italic;} | |
| 173 | -.commit-author strong, .commit-committer strong{font-weight: bold; font-style: normal;} | |
| 317 | +.commit-title{ | |
| 318 | + display: block; | |
| 319 | +} | |
| 320 | +.commit-title{ | |
| 321 | + margin-bottom: 10px; | |
| 322 | +} | |
| 323 | +.commit-author, .commit-committer{ | |
| 324 | + display: block; | |
| 325 | + color: #999; | |
| 326 | + font-weight: normal; | |
| 327 | + font-style: italic; | |
| 328 | +} | |
| 329 | +.commit-author strong, .commit-committer strong{ | |
| 330 | + font-weight: bold; | |
| 331 | + font-style: normal; | |
| 332 | +} | |
| 174 | 333 | |
| 175 | 334 | |
| 176 | -/** COMMIT ROW **/ | |
| 335 | +/** | |
| 336 | + * COMMIT ROW | |
| 337 | + */ | |
| 177 | 338 | .commit { |
| 178 | 339 | .browse_code_link_holder { |
| 179 | 340 | @extend .span2; |
| ... | ... | @@ -199,11 +360,10 @@ |
| 199 | 360 | float: left; |
| 200 | 361 | @extend .lined; |
| 201 | 362 | min-width: 65px; |
| 202 | - font-family: $monospace; | |
| 363 | + font-family: $monospace_font; | |
| 203 | 364 | } |
| 204 | 365 | } |
| 205 | 366 | |
| 206 | -.diff_file_header a, | |
| 207 | 367 | .file-stats a { |
| 208 | 368 | color: $style_color; |
| 209 | 369 | } |
| ... | ... | @@ -237,7 +397,7 @@ |
| 237 | 397 | font-size: 13px; |
| 238 | 398 | background: #474D57; |
| 239 | 399 | color: #fff; |
| 240 | - font-family: $monospace; | |
| 400 | + font-family: $monospace_font; | |
| 241 | 401 | } |
| 242 | 402 | |
| 243 | 403 | ... | ... |
app/assets/stylesheets/sections/merge_requests.scss
| ... | ... | @@ -77,7 +77,7 @@ li.merge_request { |
| 77 | 77 | font-size: 14px; |
| 78 | 78 | background: #474D57; |
| 79 | 79 | color: #fff; |
| 80 | - font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; | |
| 80 | + font-family: $monospace_font; | |
| 81 | 81 | } |
| 82 | 82 | |
| 83 | 83 | .mr_source_commit, | ... | ... |
app/assets/stylesheets/sections/notes.scss
| ... | ... | @@ -40,13 +40,13 @@ ul.notes { |
| 40 | 40 | .discussion-body { |
| 41 | 41 | margin-left: 50px; |
| 42 | 42 | |
| 43 | - .diff_file, | |
| 43 | + .file, | |
| 44 | 44 | .discussion-hidden, |
| 45 | 45 | .notes { |
| 46 | 46 | @extend .borders; |
| 47 | 47 | background-color: #F9F9F9; |
| 48 | 48 | } |
| 49 | - .diff_file .notes { | |
| 49 | + .file .notes { | |
| 50 | 50 | /* reset */ |
| 51 | 51 | background: inherit; |
| 52 | 52 | border: none; |
| ... | ... | @@ -109,7 +109,7 @@ ul.notes { |
| 109 | 109 | } |
| 110 | 110 | } |
| 111 | 111 | |
| 112 | -.diff_file .notes_holder { | |
| 112 | +.file .notes_holder { | |
| 113 | 113 | font-family: $sansFontFamily; |
| 114 | 114 | font-size: 13px; |
| 115 | 115 | line-height: 18px; |
| ... | ... | @@ -134,8 +134,6 @@ ul.notes { |
| 134 | 134 | } |
| 135 | 135 | } |
| 136 | 136 | |
| 137 | - | |
| 138 | - | |
| 139 | 137 | /** |
| 140 | 138 | * Actions for Discussions/Notes |
| 141 | 139 | */ |
| ... | ... | @@ -171,7 +169,7 @@ ul.notes { |
| 171 | 169 | } |
| 172 | 170 | } |
| 173 | 171 | } |
| 174 | -.diff_file .note .note-actions { | |
| 172 | +.file .note .note-actions { | |
| 175 | 173 | right: 0; |
| 176 | 174 | top: 0; |
| 177 | 175 | } |
| ... | ... | @@ -182,7 +180,7 @@ ul.notes { |
| 182 | 180 | * Line note button on the side of diffs |
| 183 | 181 | */ |
| 184 | 182 | |
| 185 | -.diff_file tr.line_holder { | |
| 183 | +.file tr.line_holder { | |
| 186 | 184 | .add-diff-note { |
| 187 | 185 | background: url("diff_note_add.png") no-repeat left 0; |
| 188 | 186 | height: 22px; |
| ... | ... | @@ -212,8 +210,6 @@ ul.notes { |
| 212 | 210 | } |
| 213 | 211 | } |
| 214 | 212 | |
| 215 | - | |
| 216 | - | |
| 217 | 213 | /** |
| 218 | 214 | * Note Form |
| 219 | 215 | */ |
| ... | ... | @@ -222,7 +218,12 @@ ul.notes { |
| 222 | 218 | .reply-btn { |
| 223 | 219 | @extend .save-btn; |
| 224 | 220 | } |
| 225 | -.diff_file, | |
| 221 | +.file .content tr.line_holder:hover > td { background: $hover !important; } | |
| 222 | +.file .content tr.line_holder:hover > td .line_note_link { | |
| 223 | + opacity: 1.0; | |
| 224 | + filter: alpha(opacity=100); | |
| 225 | +} | |
| 226 | +.file, | |
| 226 | 227 | .discussion { |
| 227 | 228 | .new_note { |
| 228 | 229 | margin: 8px 5px 8px 0; | ... | ... |
app/assets/stylesheets/sections/projects.scss
| ... | ... | @@ -0,0 +1,11 @@ |
| 1 | +# Provides a base class for Admin controllers to subclass | |
| 2 | +# | |
| 3 | +# Automatically sets the layout and ensures an administrator is logged in | |
| 4 | +class Admin::ApplicationController < ApplicationController | |
| 5 | + layout 'admin' | |
| 6 | + before_filter :authenticate_admin! | |
| 7 | + | |
| 8 | + def authenticate_admin! | |
| 9 | + return render_404 unless current_user.is_admin? | |
| 10 | + end | |
| 11 | +end | ... | ... |
app/controllers/admin/dashboard_controller.rb
app/controllers/admin/groups_controller.rb
app/controllers/admin/hooks_controller.rb
app/controllers/admin/logs_controller.rb
app/controllers/admin/projects/application_controller.rb
0 → 100644
| ... | ... | @@ -0,0 +1,11 @@ |
| 1 | +# Provides a base class for Admin controllers to subclass | |
| 2 | +# | |
| 3 | +# Automatically sets the layout and ensures an administrator is logged in | |
| 4 | +class Admin::Projects::ApplicationController < Admin::ApplicationController | |
| 5 | + | |
| 6 | + protected | |
| 7 | + | |
| 8 | + def project | |
| 9 | + @project ||= Project.find_with_namespace(params[:project_id]) | |
| 10 | + end | |
| 11 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,32 @@ |
| 1 | +class Admin::Projects::MembersController < Admin::Projects::ApplicationController | |
| 2 | + def edit | |
| 3 | + @member = team_member | |
| 4 | + @project = project | |
| 5 | + @team_member_relation = team_member_relation | |
| 6 | + end | |
| 7 | + | |
| 8 | + def update | |
| 9 | + if team_member_relation.update_attributes(params[:team_member]) | |
| 10 | + redirect_to [:admin, project], notice: 'Project Access was successfully updated.' | |
| 11 | + else | |
| 12 | + render action: "edit" | |
| 13 | + end | |
| 14 | + end | |
| 15 | + | |
| 16 | + def destroy | |
| 17 | + team_member_relation.destroy | |
| 18 | + | |
| 19 | + redirect_to :back | |
| 20 | + end | |
| 21 | + | |
| 22 | + private | |
| 23 | + | |
| 24 | + def team_member | |
| 25 | + @member ||= project.users.find_by_username(params[:id]) | |
| 26 | + end | |
| 27 | + | |
| 28 | + def team_member_relation | |
| 29 | + team_member.users_projects.find_by_project_id(project) | |
| 30 | + end | |
| 31 | + | |
| 32 | +end | ... | ... |
app/controllers/admin/projects_controller.rb
| 1 | -class Admin::ProjectsController < AdminController | |
| 1 | +class Admin::ProjectsController < Admin::ApplicationController | |
| 2 | 2 | before_filter :project, only: [:edit, :show, :update, :destroy, :team_update] |
| 3 | 3 | |
| 4 | 4 | def index |
| ... | ... | @@ -29,7 +29,9 @@ class Admin::ProjectsController < AdminController |
| 29 | 29 | end |
| 30 | 30 | |
| 31 | 31 | def update |
| 32 | - status = Projects::UpdateContext.new(project, current_user, params).execute(:admin) | |
| 32 | + project.creator = current_user unless project.creator | |
| 33 | + | |
| 34 | + status = ::Projects::UpdateContext.new(project, current_user, params).execute(:admin) | |
| 33 | 35 | |
| 34 | 36 | if status |
| 35 | 37 | redirect_to [:admin, @project], notice: 'Project was successfully updated.' | ... | ... |
app/controllers/admin/resque_controller.rb
app/controllers/admin/team_members_controller.rb
| ... | ... | @@ -1,22 +0,0 @@ |
| 1 | -class Admin::TeamMembersController < AdminController | |
| 2 | - def edit | |
| 3 | - @admin_team_member = UsersProject.find(params[:id]) | |
| 4 | - end | |
| 5 | - | |
| 6 | - def update | |
| 7 | - @admin_team_member = UsersProject.find(params[:id]) | |
| 8 | - | |
| 9 | - if @admin_team_member.update_attributes(params[:team_member]) | |
| 10 | - redirect_to [:admin, @admin_team_member.project], notice: 'Project Access was successfully updated.' | |
| 11 | - else | |
| 12 | - render action: "edit" | |
| 13 | - end | |
| 14 | - end | |
| 15 | - | |
| 16 | - def destroy | |
| 17 | - @admin_team_member = UsersProject.find(params[:id]) | |
| 18 | - @admin_team_member.destroy | |
| 19 | - | |
| 20 | - redirect_to :back | |
| 21 | - end | |
| 22 | -end |
| ... | ... | @@ -0,0 +1,11 @@ |
| 1 | +# Provides a base class for Admin controllers to subclass | |
| 2 | +# | |
| 3 | +# Automatically sets the layout and ensures an administrator is logged in | |
| 4 | +class Admin::Teams::ApplicationController < Admin::ApplicationController | |
| 5 | + | |
| 6 | + private | |
| 7 | + | |
| 8 | + def user_team | |
| 9 | + @team = UserTeam.find_by_path(params[:team_id]) | |
| 10 | + end | |
| 11 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,41 @@ |
| 1 | +class Admin::Teams::MembersController < Admin::Teams::ApplicationController | |
| 2 | + def new | |
| 3 | + @users = User.potential_team_members(user_team) | |
| 4 | + @users = UserDecorator.decorate @users | |
| 5 | + end | |
| 6 | + | |
| 7 | + def create | |
| 8 | + unless params[:user_ids].blank? | |
| 9 | + user_ids = params[:user_ids] | |
| 10 | + access = params[:default_project_access] | |
| 11 | + is_admin = params[:group_admin] | |
| 12 | + user_team.add_members(user_ids, access, is_admin) | |
| 13 | + end | |
| 14 | + | |
| 15 | + redirect_to admin_team_path(user_team), notice: 'Members was successfully added into Team of users.' | |
| 16 | + end | |
| 17 | + | |
| 18 | + def edit | |
| 19 | + team_member | |
| 20 | + end | |
| 21 | + | |
| 22 | + def update | |
| 23 | + options = {default_projects_access: params[:default_project_access], group_admin: params[:group_admin]} | |
| 24 | + if user_team.update_membership(team_member, options) | |
| 25 | + redirect_to admin_team_path(user_team), notice: "Membership for #{team_member.name} was successfully updated in Team of users." | |
| 26 | + else | |
| 27 | + render :edit | |
| 28 | + end | |
| 29 | + end | |
| 30 | + | |
| 31 | + def destroy | |
| 32 | + user_team.remove_member(team_member) | |
| 33 | + redirect_to admin_team_path(user_team), notice: "Member #{team_member.name} was successfully removed from Team of users." | |
| 34 | + end | |
| 35 | + | |
| 36 | + protected | |
| 37 | + | |
| 38 | + def team_member | |
| 39 | + @member ||= user_team.members.find_by_username(params[:id]) | |
| 40 | + end | |
| 41 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,41 @@ |
| 1 | +class Admin::Teams::ProjectsController < Admin::Teams::ApplicationController | |
| 2 | + def new | |
| 3 | + @projects = Project.scoped | |
| 4 | + @projects = @projects.without_team(user_team) if user_team.projects.any? | |
| 5 | + #@projects.reject!(&:empty_repo?) | |
| 6 | + end | |
| 7 | + | |
| 8 | + def create | |
| 9 | + unless params[:project_ids].blank? | |
| 10 | + project_ids = params[:project_ids] | |
| 11 | + access = params[:greatest_project_access] | |
| 12 | + user_team.assign_to_projects(project_ids, access) | |
| 13 | + end | |
| 14 | + | |
| 15 | + redirect_to admin_team_path(user_team), notice: 'Team of users was successfully assgned to projects.' | |
| 16 | + end | |
| 17 | + | |
| 18 | + def edit | |
| 19 | + team_project | |
| 20 | + end | |
| 21 | + | |
| 22 | + def update | |
| 23 | + if user_team.update_project_access(team_project, params[:greatest_project_access]) | |
| 24 | + redirect_to admin_team_path(user_team), notice: 'Access was successfully updated.' | |
| 25 | + else | |
| 26 | + render :edit | |
| 27 | + end | |
| 28 | + end | |
| 29 | + | |
| 30 | + def destroy | |
| 31 | + user_team.resign_from_project(team_project) | |
| 32 | + redirect_to admin_team_path(user_team), notice: 'Team of users was successfully reassigned from project.' | |
| 33 | + end | |
| 34 | + | |
| 35 | + protected | |
| 36 | + | |
| 37 | + def team_project | |
| 38 | + @project ||= user_team.projects.find_with_namespace(params[:id]) | |
| 39 | + end | |
| 40 | + | |
| 41 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,59 @@ |
| 1 | +class Admin::TeamsController < Admin::ApplicationController | |
| 2 | + def index | |
| 3 | + @teams = UserTeam.order('name ASC') | |
| 4 | + @teams = @teams.search(params[:name]) if params[:name].present? | |
| 5 | + @teams = @teams.page(params[:page]).per(20) | |
| 6 | + end | |
| 7 | + | |
| 8 | + def show | |
| 9 | + user_team | |
| 10 | + end | |
| 11 | + | |
| 12 | + def new | |
| 13 | + @team = UserTeam.new | |
| 14 | + end | |
| 15 | + | |
| 16 | + def edit | |
| 17 | + user_team | |
| 18 | + end | |
| 19 | + | |
| 20 | + def create | |
| 21 | + @team = UserTeam.new(params[:user_team]) | |
| 22 | + @team.path = @team.name.dup.parameterize if @team.name | |
| 23 | + @team.owner = current_user | |
| 24 | + | |
| 25 | + if @team.save | |
| 26 | + redirect_to admin_team_path(@team), notice: 'Team of users was successfully created.' | |
| 27 | + else | |
| 28 | + render action: "new" | |
| 29 | + end | |
| 30 | + end | |
| 31 | + | |
| 32 | + def update | |
| 33 | + user_team_params = params[:user_team].dup | |
| 34 | + owner_id = user_team_params.delete(:owner_id) | |
| 35 | + | |
| 36 | + if owner_id | |
| 37 | + user_team.owner = User.find(owner_id) | |
| 38 | + end | |
| 39 | + | |
| 40 | + if user_team.update_attributes(user_team_params) | |
| 41 | + redirect_to admin_team_path(user_team), notice: 'Team of users was successfully updated.' | |
| 42 | + else | |
| 43 | + render action: "edit" | |
| 44 | + end | |
| 45 | + end | |
| 46 | + | |
| 47 | + def destroy | |
| 48 | + user_team.destroy | |
| 49 | + | |
| 50 | + redirect_to admin_teams_path, notice: 'Team of users was successfully deleted.' | |
| 51 | + end | |
| 52 | + | |
| 53 | + protected | |
| 54 | + | |
| 55 | + def user_team | |
| 56 | + @team ||= UserTeam.find_by_path(params[:id]) | |
| 57 | + end | |
| 58 | + | |
| 59 | +end | ... | ... |
app/controllers/admin/users_controller.rb
| 1 | -class Admin::UsersController < AdminController | |
| 1 | +class Admin::UsersController < Admin::ApplicationController | |
| 2 | + before_filter :admin_user, only: [:show, :edit, :update, :destroy] | |
| 3 | + | |
| 2 | 4 | def index |
| 3 | 5 | @admin_users = User.scoped |
| 4 | 6 | @admin_users = @admin_users.filter(params[:filter]) |
| ... | ... | @@ -7,25 +9,18 @@ class Admin::UsersController < AdminController |
| 7 | 9 | end |
| 8 | 10 | |
| 9 | 11 | def show |
| 10 | - @admin_user = User.find(params[:id]) | |
| 11 | - | |
| 12 | - @projects = if @admin_user.authorized_projects.empty? | |
| 13 | - Project | |
| 14 | - else | |
| 15 | - Project.without_user(@admin_user) | |
| 16 | - end.all | |
| 12 | + @projects = Project.scoped | |
| 13 | + @projects = @projects.without_user(admin_user) if admin_user.authorized_projects.present? | |
| 17 | 14 | end |
| 18 | 15 | |
| 19 | 16 | def team_update |
| 20 | - @admin_user = User.find(params[:id]) | |
| 21 | - | |
| 22 | 17 | UsersProject.add_users_into_projects( |
| 23 | 18 | params[:project_ids], |
| 24 | - [@admin_user.id], | |
| 19 | + [admin_user.id], | |
| 25 | 20 | params[:project_access] |
| 26 | 21 | ) |
| 27 | 22 | |
| 28 | - redirect_to [:admin, @admin_user], notice: 'Teams were successfully updated.' | |
| 23 | + redirect_to [:admin, admin_user], notice: 'Teams were successfully updated.' | |
| 29 | 24 | end |
| 30 | 25 | |
| 31 | 26 | |
| ... | ... | @@ -34,13 +29,11 @@ class Admin::UsersController < AdminController |
| 34 | 29 | end |
| 35 | 30 | |
| 36 | 31 | def edit |
| 37 | - @admin_user = User.find(params[:id]) | |
| 32 | + admin_user | |
| 38 | 33 | end |
| 39 | 34 | |
| 40 | 35 | def block |
| 41 | - @admin_user = User.find(params[:id]) | |
| 42 | - | |
| 43 | - if @admin_user.block | |
| 36 | + if admin_user.block | |
| 44 | 37 | redirect_to :back, alert: "Successfully blocked" |
| 45 | 38 | else |
| 46 | 39 | redirect_to :back, alert: "Error occured. User was not blocked" |
| ... | ... | @@ -48,9 +41,7 @@ class Admin::UsersController < AdminController |
| 48 | 41 | end |
| 49 | 42 | |
| 50 | 43 | def unblock |
| 51 | - @admin_user = User.find(params[:id]) | |
| 52 | - | |
| 53 | - if @admin_user.update_attribute(:blocked, false) | |
| 44 | + if admin_user.update_attribute(:blocked, false) | |
| 54 | 45 | redirect_to :back, alert: "Successfully unblocked" |
| 55 | 46 | else |
| 56 | 47 | redirect_to :back, alert: "Error occured. User was not unblocked" |
| ... | ... | @@ -82,30 +73,34 @@ class Admin::UsersController < AdminController |
| 82 | 73 | params[:user].delete(:password_confirmation) |
| 83 | 74 | end |
| 84 | 75 | |
| 85 | - @admin_user = User.find(params[:id]) | |
| 86 | - @admin_user.admin = (admin && admin.to_i > 0) | |
| 76 | + admin_user.admin = (admin && admin.to_i > 0) | |
| 87 | 77 | |
| 88 | 78 | respond_to do |format| |
| 89 | - if @admin_user.update_attributes(params[:user], as: :admin) | |
| 90 | - format.html { redirect_to [:admin, @admin_user], notice: 'User was successfully updated.' } | |
| 79 | + if admin_user.update_attributes(params[:user], as: :admin) | |
| 80 | + format.html { redirect_to [:admin, admin_user], notice: 'User was successfully updated.' } | |
| 91 | 81 | format.json { head :ok } |
| 92 | 82 | else |
| 93 | 83 | format.html { render action: "edit" } |
| 94 | - format.json { render json: @admin_user.errors, status: :unprocessable_entity } | |
| 84 | + format.json { render json: admin_user.errors, status: :unprocessable_entity } | |
| 95 | 85 | end |
| 96 | 86 | end |
| 97 | 87 | end |
| 98 | 88 | |
| 99 | 89 | def destroy |
| 100 | - @admin_user = User.find(params[:id]) | |
| 101 | - if @admin_user.personal_projects.count > 0 | |
| 90 | + if admin_user.personal_projects.count > 0 | |
| 102 | 91 | redirect_to admin_users_path, alert: "User is a project owner and can't be removed." and return |
| 103 | 92 | end |
| 104 | - @admin_user.destroy | |
| 93 | + admin_user.destroy | |
| 105 | 94 | |
| 106 | 95 | respond_to do |format| |
| 107 | - format.html { redirect_to admin_users_url } | |
| 96 | + format.html { redirect_to admin_users_path } | |
| 108 | 97 | format.json { head :ok } |
| 109 | 98 | end |
| 110 | 99 | end |
| 100 | + | |
| 101 | + protected | |
| 102 | + | |
| 103 | + def admin_user | |
| 104 | + @admin_user ||= User.find_by_username!(params[:id]) | |
| 105 | + end | |
| 111 | 106 | end | ... | ... |
app/controllers/admin_controller.rb
| ... | ... | @@ -1,11 +0,0 @@ |
| 1 | -# Provides a base class for Admin controllers to subclass | |
| 2 | -# | |
| 3 | -# Automatically sets the layout and ensures an administrator is logged in | |
| 4 | -class AdminController < ApplicationController | |
| 5 | - layout 'admin' | |
| 6 | - before_filter :authenticate_admin! | |
| 7 | - | |
| 8 | - def authenticate_admin! | |
| 9 | - return render_404 unless current_user.is_admin? | |
| 10 | - end | |
| 11 | -end |
app/controllers/application_controller.rb
| ... | ... | @@ -94,6 +94,18 @@ class ApplicationController < ActionController::Base |
| 94 | 94 | return access_denied! unless can?(current_user, :download_code, project) |
| 95 | 95 | end |
| 96 | 96 | |
| 97 | + def authorize_create_team! | |
| 98 | + return access_denied! unless can?(current_user, :create_team, nil) | |
| 99 | + end | |
| 100 | + | |
| 101 | + def authorize_manage_user_team! | |
| 102 | + return access_denied! unless user_team.present? && can?(current_user, :manage_user_team, user_team) | |
| 103 | + end | |
| 104 | + | |
| 105 | + def authorize_admin_user_team! | |
| 106 | + return access_denied! unless user_team.present? && can?(current_user, :admin_user_team, user_team) | |
| 107 | + end | |
| 108 | + | |
| 97 | 109 | def access_denied! |
| 98 | 110 | render "errors/access_denied", layout: "errors", status: 404 |
| 99 | 111 | end |
| ... | ... | @@ -135,4 +147,5 @@ class ApplicationController < ActionController::Base |
| 135 | 147 | def dev_tools |
| 136 | 148 | Rack::MiniProfiler.authorize_request |
| 137 | 149 | end |
| 150 | + | |
| 138 | 151 | end | ... | ... |
app/controllers/dashboard_controller.rb
| 1 | 1 | class DashboardController < ApplicationController |
| 2 | 2 | respond_to :html |
| 3 | 3 | |
| 4 | - before_filter :projects | |
| 5 | - before_filter :event_filter, only: :index | |
| 4 | + before_filter :load_projects | |
| 5 | + before_filter :event_filter, only: :show | |
| 6 | 6 | |
| 7 | - def index | |
| 7 | + def show | |
| 8 | 8 | @groups = current_user.authorized_groups |
| 9 | - | |
| 10 | 9 | @has_authorized_projects = @projects.count > 0 |
| 11 | - | |
| 12 | - @projects = case params[:scope] | |
| 13 | - when 'personal' then | |
| 14 | - @projects.personal(current_user) | |
| 15 | - when 'joined' then | |
| 16 | - @projects.joined(current_user) | |
| 17 | - else | |
| 18 | - @projects | |
| 19 | - end | |
| 20 | - | |
| 21 | - @projects = @projects.page(params[:page]).per(30) | |
| 10 | + @teams = current_user.authorized_teams | |
| 11 | + @projects_count = @projects.count | |
| 12 | + @projects = @projects.limit(20) | |
| 22 | 13 | |
| 23 | 14 | @events = Event.in_projects(current_user.authorized_projects.pluck(:id)) |
| 24 | 15 | @events = @event_filter.apply_filter(@events) |
| ... | ... | @@ -33,6 +24,19 @@ class DashboardController < ApplicationController |
| 33 | 24 | end |
| 34 | 25 | end |
| 35 | 26 | |
| 27 | + def projects | |
| 28 | + @projects = case params[:scope] | |
| 29 | + when 'personal' then | |
| 30 | + @projects.personal(current_user) | |
| 31 | + when 'joined' then | |
| 32 | + @projects.joined(current_user) | |
| 33 | + else | |
| 34 | + @projects | |
| 35 | + end | |
| 36 | + | |
| 37 | + @projects = @projects.page(params[:page]).per(30) | |
| 38 | + end | |
| 39 | + | |
| 36 | 40 | # Get authored or assigned open merge requests |
| 37 | 41 | def merge_requests |
| 38 | 42 | @merge_requests = current_user.cared_merge_requests |
| ... | ... | @@ -55,7 +59,7 @@ class DashboardController < ApplicationController |
| 55 | 59 | |
| 56 | 60 | protected |
| 57 | 61 | |
| 58 | - def projects | |
| 62 | + def load_projects | |
| 59 | 63 | @projects = current_user.authorized_projects.sorted_by_activity |
| 60 | 64 | end |
| 61 | 65 | ... | ... |
app/controllers/groups_controller.rb
| 1 | 1 | class GroupsController < ApplicationController |
| 2 | 2 | respond_to :html |
| 3 | - layout 'group' | |
| 3 | + layout 'group', except: [:new, :create] | |
| 4 | 4 | |
| 5 | - before_filter :group | |
| 6 | - before_filter :projects | |
| 5 | + before_filter :group, except: [:new, :create] | |
| 7 | 6 | |
| 8 | 7 | # Authorize |
| 9 | - before_filter :authorize_read_group! | |
| 8 | + before_filter :authorize_read_group!, except: [:new, :create] | |
| 9 | + before_filter :authorize_create_group!, only: [:new, :create] | |
| 10 | + | |
| 11 | + # Load group projects | |
| 12 | + before_filter :projects, except: [:new, :create] | |
| 13 | + | |
| 14 | + def new | |
| 15 | + @group = Group.new | |
| 16 | + end | |
| 17 | + | |
| 18 | + def create | |
| 19 | + @group = Group.new(params[:group]) | |
| 20 | + @group.path = @group.name.dup.parameterize if @group.name | |
| 21 | + @group.owner = current_user | |
| 22 | + | |
| 23 | + if @group.save | |
| 24 | + redirect_to @group, notice: 'Group was successfully created.' | |
| 25 | + else | |
| 26 | + render action: "new" | |
| 27 | + end | |
| 28 | + end | |
| 10 | 29 | |
| 11 | 30 | def show |
| 12 | 31 | @events = Event.in_projects(project_ids).limit(20).offset(params[:offset] || 0) |
| ... | ... | @@ -85,4 +104,8 @@ class GroupsController < ApplicationController |
| 85 | 104 | return render_404 |
| 86 | 105 | end |
| 87 | 106 | end |
| 107 | + | |
| 108 | + def authorize_create_group! | |
| 109 | + can?(current_user, :create_group, nil) | |
| 110 | + end | |
| 88 | 111 | end | ... | ... |
| ... | ... | @@ -0,0 +1,27 @@ |
| 1 | +class Projects::TeamsController < Projects::ApplicationController | |
| 2 | + | |
| 3 | + def available | |
| 4 | + @teams = current_user.is_admin? ? UserTeam.scoped : current_user.user_teams | |
| 5 | + @teams = @teams.without_project(project) | |
| 6 | + unless @teams.any? | |
| 7 | + redirect_to project_team_index_path(project), notice: "No avaliable teams for assigment." | |
| 8 | + end | |
| 9 | + end | |
| 10 | + | |
| 11 | + def assign | |
| 12 | + unless params[:team_id].blank? | |
| 13 | + team = UserTeam.find(params[:team_id]) | |
| 14 | + access = params[:greatest_project_access] | |
| 15 | + team.assign_to_project(project, access) | |
| 16 | + end | |
| 17 | + redirect_to project_team_index_path(project) | |
| 18 | + end | |
| 19 | + | |
| 20 | + def resign | |
| 21 | + team = project.user_teams.find_by_path(params[:id]) | |
| 22 | + team.resign_from_project(project) | |
| 23 | + | |
| 24 | + redirect_to project_team_index_path(project) | |
| 25 | + end | |
| 26 | + | |
| 27 | +end | ... | ... |
app/controllers/projects_controller.rb
| ... | ... | @@ -19,7 +19,7 @@ class ProjectsController < ProjectResourceController |
| 19 | 19 | end |
| 20 | 20 | |
| 21 | 21 | def create |
| 22 | - @project = Projects::CreateContext.new(current_user, params[:project]).execute | |
| 22 | + @project = ::Projects::CreateContext.new(current_user, params[:project]).execute | |
| 23 | 23 | |
| 24 | 24 | respond_to do |format| |
| 25 | 25 | flash[:notice] = 'Project was successfully created.' if @project.saved? |
| ... | ... | @@ -35,7 +35,7 @@ class ProjectsController < ProjectResourceController |
| 35 | 35 | end |
| 36 | 36 | |
| 37 | 37 | def update |
| 38 | - status = Projects::UpdateContext.new(project, current_user, params).execute | |
| 38 | + status = ::Projects::UpdateContext.new(project, current_user, params).execute | |
| 39 | 39 | |
| 40 | 40 | respond_to do |format| |
| 41 | 41 | if status | ... | ... |
app/controllers/search_controller.rb
| 1 | 1 | class SearchController < ApplicationController |
| 2 | 2 | def show |
| 3 | - result = SearchContext.new(current_user.authorized_projects.map(&:id), params).execute | |
| 3 | + project_id = params[:project_id] | |
| 4 | + group_id = params[:group_id] | |
| 5 | + | |
| 6 | + project_ids = current_user.authorized_projects.map(&:id) | |
| 7 | + | |
| 8 | + if group_id.present? | |
| 9 | + group_project_ids = Group.find(group_id).projects.map(&:id) | |
| 10 | + project_ids.select! { |id| group_project_ids.include?(id)} | |
| 11 | + elsif project_id.present? | |
| 12 | + project_ids.select! { |id| id == project_id.to_i} | |
| 13 | + end | |
| 14 | + | |
| 15 | + result = SearchContext.new(project_ids, params).execute | |
| 4 | 16 | |
| 5 | 17 | @projects = result[:projects] |
| 6 | 18 | @merge_requests = result[:merge_requests] | ... | ... |
app/controllers/team_members_controller.rb
| ... | ... | @@ -4,15 +4,16 @@ class TeamMembersController < ProjectResourceController |
| 4 | 4 | before_filter :authorize_admin_project!, except: [:index, :show] |
| 5 | 5 | |
| 6 | 6 | def index |
| 7 | + @teams = UserTeam.scoped | |
| 7 | 8 | end |
| 8 | 9 | |
| 9 | 10 | def show |
| 10 | - @team_member = project.users_projects.find(params[:id]) | |
| 11 | - @events = @team_member.user.recent_events.where(:project_id => @project.id).limit(7) | |
| 11 | + @user_project_relation = project.users_projects.find_by_user_id(member) | |
| 12 | + @events = member.recent_events.in_projects(project).limit(7) | |
| 12 | 13 | end |
| 13 | 14 | |
| 14 | 15 | def new |
| 15 | - @team_member = project.users_projects.new | |
| 16 | + @user_project_relation = project.users_projects.new | |
| 16 | 17 | end |
| 17 | 18 | |
| 18 | 19 | def create |
| ... | ... | @@ -28,18 +29,18 @@ class TeamMembersController < ProjectResourceController |
| 28 | 29 | end |
| 29 | 30 | |
| 30 | 31 | def update |
| 31 | - @team_member = project.users_projects.find(params[:id]) | |
| 32 | - @team_member.update_attributes(params[:team_member]) | |
| 32 | + @user_project_relation = project.users_projects.find_by_user_id(member) | |
| 33 | + @user_project_relation.update_attributes(params[:team_member]) | |
| 33 | 34 | |
| 34 | - unless @team_member.valid? | |
| 35 | + unless @user_project_relation.valid? | |
| 35 | 36 | flash[:alert] = "User should have at least one role" |
| 36 | 37 | end |
| 37 | 38 | redirect_to project_team_index_path(@project) |
| 38 | 39 | end |
| 39 | 40 | |
| 40 | 41 | def destroy |
| 41 | - @team_member = project.users_projects.find(params[:id]) | |
| 42 | - @team_member.destroy | |
| 42 | + @user_project_relation = project.users_projects.find_by_user_id(member) | |
| 43 | + @user_project_relation.destroy | |
| 43 | 44 | |
| 44 | 45 | respond_to do |format| |
| 45 | 46 | format.html { redirect_to project_team_index_path(@project) } |
| ... | ... | @@ -54,4 +55,10 @@ class TeamMembersController < ProjectResourceController |
| 54 | 55 | |
| 55 | 56 | redirect_to project_team_members_path(project), notice: notice |
| 56 | 57 | end |
| 58 | + | |
| 59 | + protected | |
| 60 | + | |
| 61 | + def member | |
| 62 | + @member ||= User.find_by_username(params[:id]) | |
| 63 | + end | |
| 57 | 64 | end | ... | ... |
| ... | ... | @@ -0,0 +1,49 @@ |
| 1 | +class Teams::MembersController < Teams::ApplicationController | |
| 2 | + | |
| 3 | + skip_before_filter :authorize_manage_user_team!, only: [:index] | |
| 4 | + | |
| 5 | + def index | |
| 6 | + @members = user_team.members | |
| 7 | + end | |
| 8 | + | |
| 9 | + def new | |
| 10 | + @users = User.potential_team_members(user_team) | |
| 11 | + @users = UserDecorator.decorate @users | |
| 12 | + end | |
| 13 | + | |
| 14 | + def create | |
| 15 | + unless params[:user_ids].blank? | |
| 16 | + user_ids = params[:user_ids] | |
| 17 | + access = params[:default_project_access] | |
| 18 | + is_admin = params[:group_admin] | |
| 19 | + user_team.add_members(user_ids, access, is_admin) | |
| 20 | + end | |
| 21 | + | |
| 22 | + redirect_to team_members_path(user_team), notice: 'Members was successfully added into Team of users.' | |
| 23 | + end | |
| 24 | + | |
| 25 | + def edit | |
| 26 | + team_member | |
| 27 | + end | |
| 28 | + | |
| 29 | + def update | |
| 30 | + options = {default_projects_access: params[:default_project_access], group_admin: params[:group_admin]} | |
| 31 | + if user_team.update_membership(team_member, options) | |
| 32 | + redirect_to team_members_path(user_team), notice: "Membership for #{team_member.name} was successfully updated in Team of users." | |
| 33 | + else | |
| 34 | + render :edit | |
| 35 | + end | |
| 36 | + end | |
| 37 | + | |
| 38 | + def destroy | |
| 39 | + user_team.remove_member(team_member) | |
| 40 | + redirect_to team_path(user_team), notice: "Member #{team_member.name} was successfully removed from Team of users." | |
| 41 | + end | |
| 42 | + | |
| 43 | + protected | |
| 44 | + | |
| 45 | + def team_member | |
| 46 | + @member ||= user_team.members.find_by_username(params[:id]) | |
| 47 | + end | |
| 48 | + | |
| 49 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,57 @@ |
| 1 | +class Teams::ProjectsController < Teams::ApplicationController | |
| 2 | + | |
| 3 | + skip_before_filter :authorize_manage_user_team!, only: [:index] | |
| 4 | + | |
| 5 | + def index | |
| 6 | + @projects = user_team.projects | |
| 7 | + @avaliable_projects = current_user.admin? ? Project.without_team(user_team) : current_user.owned_projects.without_team(user_team) | |
| 8 | + end | |
| 9 | + | |
| 10 | + def new | |
| 11 | + user_team | |
| 12 | + @avaliable_projects = current_user.owned_projects.scoped | |
| 13 | + @avaliable_projects = @avaliable_projects.without_team(user_team) if user_team.projects.any? | |
| 14 | + | |
| 15 | + redirect_to team_projects_path(user_team), notice: "No avalible projects." unless @avaliable_projects.any? | |
| 16 | + end | |
| 17 | + | |
| 18 | + def create | |
| 19 | + redirect_to :back if params[:project_ids].blank? | |
| 20 | + | |
| 21 | + project_ids = params[:project_ids] | |
| 22 | + access = params[:greatest_project_access] | |
| 23 | + | |
| 24 | + # Reject non-allowed projects | |
| 25 | + allowed_project_ids = current_user.owned_projects.map(&:id) | |
| 26 | + project_ids.select! { |id| allowed_project_ids.include?(id.to_i) } | |
| 27 | + | |
| 28 | + # Assign projects to team | |
| 29 | + user_team.assign_to_projects(project_ids, access) | |
| 30 | + | |
| 31 | + redirect_to team_projects_path(user_team), notice: 'Team of users was successfully assigned to projects.' | |
| 32 | + end | |
| 33 | + | |
| 34 | + def edit | |
| 35 | + team_project | |
| 36 | + end | |
| 37 | + | |
| 38 | + def update | |
| 39 | + if user_team.update_project_access(team_project, params[:greatest_project_access]) | |
| 40 | + redirect_to team_projects_path(user_team), notice: 'Access was successfully updated.' | |
| 41 | + else | |
| 42 | + render :edit | |
| 43 | + end | |
| 44 | + end | |
| 45 | + | |
| 46 | + def destroy | |
| 47 | + user_team.resign_from_project(team_project) | |
| 48 | + redirect_to team_projects_path(user_team), notice: 'Team of users was successfully reassigned from project.' | |
| 49 | + end | |
| 50 | + | |
| 51 | + private | |
| 52 | + | |
| 53 | + def team_project | |
| 54 | + @project ||= user_team.projects.find_with_namespace(params[:id]) | |
| 55 | + end | |
| 56 | + | |
| 57 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,76 @@ |
| 1 | +class TeamsController < ApplicationController | |
| 2 | + # Authorize | |
| 3 | + before_filter :authorize_create_team!, only: [:new, :create] | |
| 4 | + before_filter :authorize_manage_user_team!, only: [:edit, :update] | |
| 5 | + before_filter :authorize_admin_user_team!, only: [:destroy] | |
| 6 | + | |
| 7 | + before_filter :user_team, except: [:new, :create] | |
| 8 | + | |
| 9 | + layout 'user_team', except: [:new, :create] | |
| 10 | + | |
| 11 | + def show | |
| 12 | + user_team | |
| 13 | + projects | |
| 14 | + @events = Event.in_projects(user_team.project_ids).limit(20).offset(params[:offset] || 0) | |
| 15 | + end | |
| 16 | + | |
| 17 | + def edit | |
| 18 | + user_team | |
| 19 | + end | |
| 20 | + | |
| 21 | + def update | |
| 22 | + if user_team.update_attributes(params[:user_team]) | |
| 23 | + redirect_to team_path(user_team) | |
| 24 | + else | |
| 25 | + render action: :edit | |
| 26 | + end | |
| 27 | + end | |
| 28 | + | |
| 29 | + def destroy | |
| 30 | + user_team.destroy | |
| 31 | + redirect_to dashboard_path | |
| 32 | + end | |
| 33 | + | |
| 34 | + def new | |
| 35 | + @team = UserTeam.new | |
| 36 | + end | |
| 37 | + | |
| 38 | + def create | |
| 39 | + @team = UserTeam.new(params[:user_team]) | |
| 40 | + @team.owner = current_user unless params[:owner] | |
| 41 | + @team.path = @team.name.dup.parameterize if @team.name | |
| 42 | + | |
| 43 | + if @team.save | |
| 44 | + redirect_to team_path(@team) | |
| 45 | + else | |
| 46 | + render action: :new | |
| 47 | + end | |
| 48 | + end | |
| 49 | + | |
| 50 | + # Get authored or assigned open merge requests | |
| 51 | + def merge_requests | |
| 52 | + projects | |
| 53 | + @merge_requests = MergeRequest.of_user_team(user_team) | |
| 54 | + @merge_requests = FilterContext.new(@merge_requests, params).execute | |
| 55 | + @merge_requests = @merge_requests.recent.page(params[:page]).per(20) | |
| 56 | + end | |
| 57 | + | |
| 58 | + # Get only assigned issues | |
| 59 | + def issues | |
| 60 | + projects | |
| 61 | + @issues = Issue.of_user_team(user_team) | |
| 62 | + @issues = FilterContext.new(@issues, params).execute | |
| 63 | + @issues = @issues.recent.page(params[:page]).per(20) | |
| 64 | + @issues = @issues.includes(:author, :project) | |
| 65 | + end | |
| 66 | + | |
| 67 | + protected | |
| 68 | + | |
| 69 | + def projects | |
| 70 | + @projects ||= user_team.projects.sorted_by_activity | |
| 71 | + end | |
| 72 | + | |
| 73 | + def user_team | |
| 74 | + @team ||= current_user.authorized_teams.find_by_path(params[:id]) | |
| 75 | + end | |
| 76 | +end | ... | ... |
app/decorators/user_decorator.rb
app/helpers/application_helper.rb
| ... | ... | @@ -72,8 +72,9 @@ module ApplicationHelper |
| 72 | 72 | end |
| 73 | 73 | |
| 74 | 74 | def search_autocomplete_source |
| 75 | - projects = current_user.authorized_projects.map { |p| { label: p.name_with_namespace, url: project_path(p) } } | |
| 76 | - groups = current_user.authorized_groups.map { |group| { label: "<group> #{group.name}", url: group_path(group) } } | |
| 75 | + projects = current_user.authorized_projects.map { |p| { label: "project: #{p.name_with_namespace}", url: project_path(p) } } | |
| 76 | + groups = current_user.authorized_groups.map { |group| { label: "group: #{group.name}", url: group_path(group) } } | |
| 77 | + teams = current_user.authorized_teams.map { |team| { label: "team: #{team.name}", url: team_path(team) } } | |
| 77 | 78 | |
| 78 | 79 | default_nav = [ |
| 79 | 80 | { label: "My Profile", url: profile_path }, |
| ... | ... | @@ -83,29 +84,29 @@ module ApplicationHelper |
| 83 | 84 | ] |
| 84 | 85 | |
| 85 | 86 | help_nav = [ |
| 86 | - { label: "API Help", url: help_api_path }, | |
| 87 | - { label: "Markdown Help", url: help_markdown_path }, | |
| 88 | - { label: "Permissions Help", url: help_permissions_path }, | |
| 89 | - { label: "Public Access Help", url: help_public_access_path }, | |
| 90 | - { label: "Rake Tasks Help", url: help_raketasks_path }, | |
| 91 | - { label: "SSH Keys Help", url: help_ssh_path }, | |
| 92 | - { label: "System Hooks Help", url: help_system_hooks_path }, | |
| 93 | - { label: "Web Hooks Help", url: help_web_hooks_path }, | |
| 94 | - { label: "Workflow Help", url: help_workflow_path }, | |
| 87 | + { label: "help: API Help", url: help_api_path }, | |
| 88 | + { label: "help: Markdown Help", url: help_markdown_path }, | |
| 89 | + { label: "help: Permissions Help", url: help_permissions_path }, | |
| 90 | + { label: "help: Public Access Help", url: help_public_access_path }, | |
| 91 | + { label: "help: Rake Tasks Help", url: help_raketasks_path }, | |
| 92 | + { label: "help: SSH Keys Help", url: help_ssh_path }, | |
| 93 | + { label: "help: System Hooks Help", url: help_system_hooks_path }, | |
| 94 | + { label: "help: Web Hooks Help", url: help_web_hooks_path }, | |
| 95 | + { label: "help: Workflow Help", url: help_workflow_path }, | |
| 95 | 96 | ] |
| 96 | 97 | |
| 97 | 98 | project_nav = [] |
| 98 | 99 | if @project && @project.repository && @project.repository.root_ref |
| 99 | 100 | project_nav = [ |
| 100 | - { label: "#{@project.name} Issues", url: project_issues_path(@project) }, | |
| 101 | - { label: "#{@project.name} Commits", url: project_commits_path(@project, @ref || @project.repository.root_ref) }, | |
| 102 | - { label: "#{@project.name} Merge Requests", url: project_merge_requests_path(@project) }, | |
| 103 | - { label: "#{@project.name} Milestones", url: project_milestones_path(@project) }, | |
| 104 | - { label: "#{@project.name} Snippets", url: project_snippets_path(@project) }, | |
| 105 | - { label: "#{@project.name} Team", url: project_team_index_path(@project) }, | |
| 106 | - { label: "#{@project.name} Tree", url: project_tree_path(@project, @ref || @project.repository.root_ref) }, | |
| 107 | - { label: "#{@project.name} Wall", url: wall_project_path(@project) }, | |
| 108 | - { label: "#{@project.name} Wiki", url: project_wikis_path(@project) }, | |
| 101 | + { label: "#{@project.name_with_namespace} - Issues", url: project_issues_path(@project) }, | |
| 102 | + { label: "#{@project.name_with_namespace} - Commits", url: project_commits_path(@project, @ref || @project.repository.root_ref) }, | |
| 103 | + { label: "#{@project.name_with_namespace} - Merge Requests", url: project_merge_requests_path(@project) }, | |
| 104 | + { label: "#{@project.name_with_namespace} - Milestones", url: project_milestones_path(@project) }, | |
| 105 | + { label: "#{@project.name_with_namespace} - Snippets", url: project_snippets_path(@project) }, | |
| 106 | + { label: "#{@project.name_with_namespace} - Team", url: project_team_index_path(@project) }, | |
| 107 | + { label: "#{@project.name_with_namespace} - Tree", url: project_tree_path(@project, @ref || @project.repository.root_ref) }, | |
| 108 | + { label: "#{@project.name_with_namespace} - Wall", url: wall_project_path(@project) }, | |
| 109 | + { label: "#{@project.name_with_namespace} - Wiki", url: project_wikis_path(@project) }, | |
| 109 | 110 | ] |
| 110 | 111 | end |
| 111 | 112 | ... | ... |
app/helpers/commits_helper.rb
app/helpers/dashboard_helper.rb
| ... | ... | @@ -9,9 +9,9 @@ module DashboardHelper |
| 9 | 9 | |
| 10 | 10 | case entity |
| 11 | 11 | when 'issue' then |
| 12 | - dashboard_issues_path(options) | |
| 12 | + issues_dashboard_path(options) | |
| 13 | 13 | when 'merge_request' |
| 14 | - dashboard_merge_requests_path(options) | |
| 14 | + merge_requests_dashboard_path(options) | |
| 15 | 15 | end |
| 16 | 16 | end |
| 17 | 17 | ... | ... |
app/helpers/projects_helper.rb
| ... | ... | @@ -3,8 +3,12 @@ module ProjectsHelper |
| 3 | 3 | @project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access) |
| 4 | 4 | end |
| 5 | 5 | |
| 6 | - def remove_from_team_message(project, member) | |
| 7 | - "You are going to remove #{member.user_name} from #{project.name}. Are you sure?" | |
| 6 | + def grouper_project_teams(project) | |
| 7 | + @project.user_team_project_relationships.sort_by(&:greatest_access).reverse.group_by(&:greatest_access) | |
| 8 | + end | |
| 9 | + | |
| 10 | + def remove_from_project_team_message(project, user) | |
| 11 | + "You are going to remove #{user.name} from #{project.name} project team. Are you sure?" | |
| 8 | 12 | end |
| 9 | 13 | |
| 10 | 14 | def link_to_project project |
| ... | ... | @@ -51,7 +55,9 @@ module ProjectsHelper |
| 51 | 55 | |
| 52 | 56 | def project_title project |
| 53 | 57 | if project.group |
| 54 | - project.name_with_namespace | |
| 58 | + content_tag :span do | |
| 59 | + link_to(project.group.name, group_path(project.group)) + " / " + project.name | |
| 60 | + end | |
| 55 | 61 | else |
| 56 | 62 | project.name |
| 57 | 63 | end | ... | ... |
app/helpers/tab_helper.rb
| ... | ... | @@ -39,7 +39,12 @@ module TabHelper |
| 39 | 39 | # Returns a list item element String |
| 40 | 40 | def nav_link(options = {}, &block) |
| 41 | 41 | if path = options.delete(:path) |
| 42 | - c, a, _ = path.split('#') | |
| 42 | + if path.respond_to?(:each) | |
| 43 | + c = path.map { |p| p.split('#').first } | |
| 44 | + a = path.map { |p| p.split('#').last } | |
| 45 | + else | |
| 46 | + c, a, _ = path.split('#') | |
| 47 | + end | |
| 43 | 48 | else |
| 44 | 49 | c = options.delete(:controller) |
| 45 | 50 | a = options.delete(:action) | ... | ... |
| ... | ... | @@ -0,0 +1,26 @@ |
| 1 | +module UserTeamsHelper | |
| 2 | + def team_filter_path(entity, options={}) | |
| 3 | + exist_opts = { | |
| 4 | + status: params[:status], | |
| 5 | + project_id: params[:project_id], | |
| 6 | + } | |
| 7 | + | |
| 8 | + options = exist_opts.merge(options) | |
| 9 | + | |
| 10 | + case entity | |
| 11 | + when 'issue' then | |
| 12 | + issues_team_path(@team, options) | |
| 13 | + when 'merge_request' | |
| 14 | + merge_requests_team_path(@team, options) | |
| 15 | + end | |
| 16 | + end | |
| 17 | + | |
| 18 | + def grouped_user_team_members(team) | |
| 19 | + team.user_team_user_relationships.sort_by(&:permission).reverse.group_by(&:permission) | |
| 20 | + end | |
| 21 | + | |
| 22 | + def remove_from_user_team_message(team, member) | |
| 23 | + "You are going to remove #{member.name} from #{team.name}. Are you sure?" | |
| 24 | + end | |
| 25 | + | |
| 26 | +end | ... | ... |
app/models/ability.rb
| 1 | 1 | class Ability |
| 2 | 2 | class << self |
| 3 | - def allowed(object, subject) | |
| 3 | + def allowed(user, subject) | |
| 4 | + return [] unless user.kind_of?(User) | |
| 5 | + | |
| 4 | 6 | case subject.class.name |
| 5 | - when "Project" then project_abilities(object, subject) | |
| 6 | - when "Issue" then issue_abilities(object, subject) | |
| 7 | - when "Note" then note_abilities(object, subject) | |
| 8 | - when "Snippet" then snippet_abilities(object, subject) | |
| 9 | - when "MergeRequest" then merge_request_abilities(object, subject) | |
| 10 | - when "Group", "Namespace" then group_abilities(object, subject) | |
| 7 | + when "Project" then project_abilities(user, subject) | |
| 8 | + when "Issue" then issue_abilities(user, subject) | |
| 9 | + when "Note" then note_abilities(user, subject) | |
| 10 | + when "Snippet" then snippet_abilities(user, subject) | |
| 11 | + when "MergeRequest" then merge_request_abilities(user, subject) | |
| 12 | + when "Group", "Namespace" then group_abilities(user, subject) | |
| 13 | + when "UserTeam" then user_team_abilities(user, subject) | |
| 11 | 14 | else [] |
| 12 | - end | |
| 15 | + end.concat(global_abilities(user)) | |
| 16 | + end | |
| 17 | + | |
| 18 | + def global_abilities(user) | |
| 19 | + rules = [] | |
| 20 | + rules << :create_group if user.can_create_group | |
| 21 | + rules << :create_team if user.can_create_team | |
| 22 | + rules | |
| 13 | 23 | end |
| 14 | 24 | |
| 15 | 25 | def project_abilities(user, project) |
| ... | ... | @@ -110,6 +120,22 @@ class Ability |
| 110 | 120 | rules.flatten |
| 111 | 121 | end |
| 112 | 122 | |
| 123 | + def user_team_abilities user, team | |
| 124 | + rules = [] | |
| 125 | + | |
| 126 | + # Only group owner and administrators can manage group | |
| 127 | + if team.owner == user || team.admin?(user) || user.admin? | |
| 128 | + rules << [ :manage_user_team ] | |
| 129 | + end | |
| 130 | + | |
| 131 | + if team.owner == user || user.admin? | |
| 132 | + rules << [ :admin_user_team ] | |
| 133 | + end | |
| 134 | + | |
| 135 | + rules.flatten | |
| 136 | + end | |
| 137 | + | |
| 138 | + | |
| 113 | 139 | [:issue, :note, :snippet, :merge_request].each do |name| |
| 114 | 140 | define_method "#{name}_abilities" do |user, subject| |
| 115 | 141 | if subject.author == user | ... | ... |
app/models/concerns/issuable.rb
| ... | ... | @@ -22,6 +22,7 @@ module Issuable |
| 22 | 22 | scope :opened, where(closed: false) |
| 23 | 23 | scope :closed, where(closed: true) |
| 24 | 24 | scope :of_group, ->(group) { where(project_id: group.project_ids) } |
| 25 | + scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) } | |
| 25 | 26 | scope :assigned, ->(u) { where(assignee_id: u.id)} |
| 26 | 27 | scope :recent, order("created_at DESC") |
| 27 | 28 | ... | ... |
app/models/project.rb
| ... | ... | @@ -33,28 +33,31 @@ class Project < ActiveRecord::Base |
| 33 | 33 | attr_accessor :error_code |
| 34 | 34 | |
| 35 | 35 | # Relations |
| 36 | - belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'" | |
| 36 | + belongs_to :creator, foreign_key: "creator_id", class_name: "User" | |
| 37 | + belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'" | |
| 37 | 38 | belongs_to :namespace |
| 38 | 39 | |
| 39 | - belongs_to :creator, | |
| 40 | - class_name: "User", | |
| 41 | - foreign_key: "creator_id" | |
| 42 | - | |
| 43 | - has_many :users, through: :users_projects | |
| 44 | - has_many :events, dependent: :destroy | |
| 45 | - has_many :merge_requests, dependent: :destroy | |
| 46 | - has_many :issues, dependent: :destroy, order: "closed, created_at DESC" | |
| 47 | - has_many :milestones, dependent: :destroy | |
| 48 | - has_many :users_projects, dependent: :destroy | |
| 49 | - has_many :notes, dependent: :destroy | |
| 50 | - has_many :snippets, dependent: :destroy | |
| 51 | - has_many :deploy_keys, dependent: :destroy, foreign_key: "project_id", class_name: "Key" | |
| 52 | - has_many :hooks, dependent: :destroy, class_name: "ProjectHook" | |
| 53 | - has_many :wikis, dependent: :destroy | |
| 54 | - has_many :protected_branches, dependent: :destroy | |
| 55 | 40 | has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', foreign_key: 'project_id' |
| 56 | 41 | has_one :gitlab_ci_service, dependent: :destroy |
| 57 | 42 | |
| 43 | + has_many :events, dependent: :destroy | |
| 44 | + has_many :merge_requests, dependent: :destroy | |
| 45 | + has_many :issues, dependent: :destroy, order: "closed, created_at DESC" | |
| 46 | + has_many :milestones, dependent: :destroy | |
| 47 | + has_many :users_projects, dependent: :destroy | |
| 48 | + has_many :notes, dependent: :destroy | |
| 49 | + has_many :snippets, dependent: :destroy | |
| 50 | + has_many :deploy_keys, dependent: :destroy, class_name: "Key", foreign_key: "project_id" | |
| 51 | + has_many :hooks, dependent: :destroy, class_name: "ProjectHook" | |
| 52 | + has_many :wikis, dependent: :destroy | |
| 53 | + has_many :protected_branches, dependent: :destroy | |
| 54 | + has_many :user_team_project_relationships, dependent: :destroy | |
| 55 | + | |
| 56 | + has_many :users, through: :users_projects | |
| 57 | + has_many :user_teams, through: :user_team_project_relationships | |
| 58 | + has_many :user_team_user_relationships, through: :user_teams | |
| 59 | + has_many :user_teams_members, through: :user_team_user_relationships | |
| 60 | + | |
| 58 | 61 | delegate :name, to: :owner, allow_nil: true, prefix: true |
| 59 | 62 | |
| 60 | 63 | # Validations |
| ... | ... | @@ -77,6 +80,8 @@ class Project < ActiveRecord::Base |
| 77 | 80 | # Scopes |
| 78 | 81 | scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) } |
| 79 | 82 | scope :not_in_group, ->(group) { where("id NOT IN (:ids)", ids: group.project_ids ) } |
| 83 | + scope :without_team, ->(team) { team.projects.present? ? where("id NOT IN (:ids)", ids: team.projects.map(&:id)) : scoped } | |
| 84 | + scope :in_team, ->(team) { where("id IN (:ids)", ids: team.projects.map(&:id)) } | |
| 80 | 85 | scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) } |
| 81 | 86 | scope :sorted_by_activity, ->() { order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") } |
| 82 | 87 | scope :personal, ->(user) { where(namespace_id: user.namespace_id) } |
| ... | ... | @@ -122,7 +127,7 @@ class Project < ActiveRecord::Base |
| 122 | 127 | end |
| 123 | 128 | |
| 124 | 129 | def team |
| 125 | - @team ||= Team.new(self) | |
| 130 | + @team ||= ProjectTeam.new(self) | |
| 126 | 131 | end |
| 127 | 132 | |
| 128 | 133 | def repository |
| ... | ... | @@ -335,7 +340,7 @@ class Project < ActiveRecord::Base |
| 335 | 340 | end |
| 336 | 341 | |
| 337 | 342 | def execute_hooks(data) |
| 338 | - hooks.each { |hook| hook.execute(data) } | |
| 343 | + hooks.each { |hook| hook.async_execute(data) } | |
| 339 | 344 | end |
| 340 | 345 | |
| 341 | 346 | def execute_services(data) |
| ... | ... | @@ -489,6 +494,11 @@ class Project < ActiveRecord::Base |
| 489 | 494 | http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') |
| 490 | 495 | end |
| 491 | 496 | |
| 497 | + def project_access_human(member) | |
| 498 | + project_user_relation = self.users_projects.find_by_user_id(member.id) | |
| 499 | + self.class.access_options.key(project_user_relation.project_access) | |
| 500 | + end | |
| 501 | + | |
| 492 | 502 | # Check if current branch name is marked as protected in the system |
| 493 | 503 | def protected_branch? branch_name |
| 494 | 504 | protected_branches.map(&:name).include?(branch_name) | ... | ... |
| ... | ... | @@ -0,0 +1,122 @@ |
| 1 | +class ProjectTeam | |
| 2 | + attr_accessor :project | |
| 3 | + | |
| 4 | + def initialize(project) | |
| 5 | + @project = project | |
| 6 | + end | |
| 7 | + | |
| 8 | + # Shortcut to add users | |
| 9 | + # | |
| 10 | + # Use: | |
| 11 | + # @team << [@user, :master] | |
| 12 | + # @team << [@users, :master] | |
| 13 | + # | |
| 14 | + def << args | |
| 15 | + users = args.first | |
| 16 | + | |
| 17 | + if users.respond_to?(:each) | |
| 18 | + add_users(users, args.second) | |
| 19 | + else | |
| 20 | + add_user(users, args.second) | |
| 21 | + end | |
| 22 | + end | |
| 23 | + | |
| 24 | + def get_tm user_id | |
| 25 | + project.users_projects.find_by_user_id(user_id) | |
| 26 | + end | |
| 27 | + | |
| 28 | + def add_user(user, access) | |
| 29 | + add_users_ids([user.id], access) | |
| 30 | + end | |
| 31 | + | |
| 32 | + def add_users(users, access) | |
| 33 | + add_users_ids(users.map(&:id), access) | |
| 34 | + end | |
| 35 | + | |
| 36 | + def add_users_ids(user_ids, access) | |
| 37 | + UsersProject.add_users_into_projects( | |
| 38 | + [project.id], | |
| 39 | + user_ids, | |
| 40 | + access | |
| 41 | + ) | |
| 42 | + end | |
| 43 | + | |
| 44 | + # Remove all users from project team | |
| 45 | + def truncate | |
| 46 | + UsersProject.truncate_team(project) | |
| 47 | + end | |
| 48 | + | |
| 49 | + def members | |
| 50 | + project.users_projects | |
| 51 | + end | |
| 52 | + | |
| 53 | + def guests | |
| 54 | + members.guests.map(&:user) | |
| 55 | + end | |
| 56 | + | |
| 57 | + def reporters | |
| 58 | + members.reporters.map(&:user) | |
| 59 | + end | |
| 60 | + | |
| 61 | + def developers | |
| 62 | + members.developers.map(&:user) | |
| 63 | + end | |
| 64 | + | |
| 65 | + def masters | |
| 66 | + members.masters.map(&:user) | |
| 67 | + end | |
| 68 | + | |
| 69 | + def repository_readers | |
| 70 | + repository_members[UsersProject::REPORTER] | |
| 71 | + end | |
| 72 | + | |
| 73 | + def repository_writers | |
| 74 | + repository_members[UsersProject::DEVELOPER] | |
| 75 | + end | |
| 76 | + | |
| 77 | + def repository_masters | |
| 78 | + repository_members[UsersProject::MASTER] | |
| 79 | + end | |
| 80 | + | |
| 81 | + def repository_members | |
| 82 | + keys = Hash.new {|h,k| h[k] = [] } | |
| 83 | + UsersProject.select("keys.identifier, project_access"). | |
| 84 | + joins(user: :keys).where(project_id: project.id). | |
| 85 | + each {|row| keys[row.project_access] << [row.identifier] } | |
| 86 | + | |
| 87 | + keys[UsersProject::REPORTER] += project.deploy_keys.pluck(:identifier) | |
| 88 | + keys | |
| 89 | + end | |
| 90 | + | |
| 91 | + def import(source_project) | |
| 92 | + target_project = project | |
| 93 | + | |
| 94 | + source_team = source_project.users_projects.all | |
| 95 | + target_team = target_project.users_projects.all | |
| 96 | + target_user_ids = target_team.map(&:user_id) | |
| 97 | + | |
| 98 | + source_team.reject! do |tm| | |
| 99 | + # Skip if user already present in team | |
| 100 | + target_user_ids.include?(tm.user_id) | |
| 101 | + end | |
| 102 | + | |
| 103 | + source_team.map! do |tm| | |
| 104 | + new_tm = tm.dup | |
| 105 | + new_tm.id = nil | |
| 106 | + new_tm.project_id = target_project.id | |
| 107 | + new_tm.skip_git = true | |
| 108 | + new_tm | |
| 109 | + end | |
| 110 | + | |
| 111 | + UsersProject.transaction do | |
| 112 | + source_team.each do |tm| | |
| 113 | + tm.save | |
| 114 | + end | |
| 115 | + target_project.update_repository | |
| 116 | + end | |
| 117 | + | |
| 118 | + true | |
| 119 | + rescue | |
| 120 | + false | |
| 121 | + end | |
| 122 | +end | ... | ... |
app/models/team.rb
| ... | ... | @@ -1,122 +0,0 @@ |
| 1 | -class Team | |
| 2 | - attr_accessor :project | |
| 3 | - | |
| 4 | - def initialize(project) | |
| 5 | - @project = project | |
| 6 | - end | |
| 7 | - | |
| 8 | - # Shortcut to add users | |
| 9 | - # | |
| 10 | - # Use: | |
| 11 | - # @team << [@user, :master] | |
| 12 | - # @team << [@users, :master] | |
| 13 | - # | |
| 14 | - def << args | |
| 15 | - users = args.first | |
| 16 | - | |
| 17 | - if users.respond_to?(:each) | |
| 18 | - add_users(users, args.second) | |
| 19 | - else | |
| 20 | - add_user(users, args.second) | |
| 21 | - end | |
| 22 | - end | |
| 23 | - | |
| 24 | - def get_tm user_id | |
| 25 | - project.users_projects.find_by_user_id(user_id) | |
| 26 | - end | |
| 27 | - | |
| 28 | - def add_user(user, access) | |
| 29 | - add_users_ids([user.id], access) | |
| 30 | - end | |
| 31 | - | |
| 32 | - def add_users(users, access) | |
| 33 | - add_users_ids(users.map(&:id), access) | |
| 34 | - end | |
| 35 | - | |
| 36 | - def add_users_ids(user_ids, access) | |
| 37 | - UsersProject.add_users_into_projects( | |
| 38 | - [project.id], | |
| 39 | - user_ids, | |
| 40 | - access | |
| 41 | - ) | |
| 42 | - end | |
| 43 | - | |
| 44 | - # Remove all users from project team | |
| 45 | - def truncate | |
| 46 | - UsersProject.truncate_team(project) | |
| 47 | - end | |
| 48 | - | |
| 49 | - def members | |
| 50 | - project.users_projects | |
| 51 | - end | |
| 52 | - | |
| 53 | - def guests | |
| 54 | - members.guests.map(&:user) | |
| 55 | - end | |
| 56 | - | |
| 57 | - def reporters | |
| 58 | - members.reporters.map(&:user) | |
| 59 | - end | |
| 60 | - | |
| 61 | - def developers | |
| 62 | - members.developers.map(&:user) | |
| 63 | - end | |
| 64 | - | |
| 65 | - def masters | |
| 66 | - members.masters.map(&:user) | |
| 67 | - end | |
| 68 | - | |
| 69 | - def repository_readers | |
| 70 | - repository_members[UsersProject::REPORTER] | |
| 71 | - end | |
| 72 | - | |
| 73 | - def repository_writers | |
| 74 | - repository_members[UsersProject::DEVELOPER] | |
| 75 | - end | |
| 76 | - | |
| 77 | - def repository_masters | |
| 78 | - repository_members[UsersProject::MASTER] | |
| 79 | - end | |
| 80 | - | |
| 81 | - def repository_members | |
| 82 | - keys = Hash.new {|h,k| h[k] = [] } | |
| 83 | - UsersProject.select("keys.identifier, project_access"). | |
| 84 | - joins(user: :keys).where(project_id: project.id). | |
| 85 | - each {|row| keys[row.project_access] << [row.identifier] } | |
| 86 | - | |
| 87 | - keys[UsersProject::REPORTER] += project.deploy_keys.pluck(:identifier) | |
| 88 | - keys | |
| 89 | - end | |
| 90 | - | |
| 91 | - def import(source_project) | |
| 92 | - target_project = project | |
| 93 | - | |
| 94 | - source_team = source_project.users_projects.all | |
| 95 | - target_team = target_project.users_projects.all | |
| 96 | - target_user_ids = target_team.map(&:user_id) | |
| 97 | - | |
| 98 | - source_team.reject! do |tm| | |
| 99 | - # Skip if user already present in team | |
| 100 | - target_user_ids.include?(tm.user_id) | |
| 101 | - end | |
| 102 | - | |
| 103 | - source_team.map! do |tm| | |
| 104 | - new_tm = tm.dup | |
| 105 | - new_tm.id = nil | |
| 106 | - new_tm.project_id = target_project.id | |
| 107 | - new_tm.skip_git = true | |
| 108 | - new_tm | |
| 109 | - end | |
| 110 | - | |
| 111 | - UsersProject.transaction do | |
| 112 | - source_team.each do |tm| | |
| 113 | - tm.save | |
| 114 | - end | |
| 115 | - target_project.update_repository | |
| 116 | - end | |
| 117 | - | |
| 118 | - true | |
| 119 | - rescue | |
| 120 | - false | |
| 121 | - end | |
| 122 | -end |
app/models/user.rb
| ... | ... | @@ -40,23 +40,32 @@ class User < ActiveRecord::Base |
| 40 | 40 | attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username, |
| 41 | 41 | :skype, :linkedin, :twitter, :dark_scheme, :theme_id, :force_random_password, |
| 42 | 42 | :extern_uid, :provider, as: [:default, :admin] |
| 43 | - attr_accessible :projects_limit, as: :admin | |
| 43 | + attr_accessible :projects_limit, :can_create_team, :can_create_group, as: :admin | |
| 44 | 44 | |
| 45 | 45 | attr_accessor :force_random_password |
| 46 | 46 | |
| 47 | 47 | # Namespace for personal projects |
| 48 | - has_one :namespace, class_name: "Namespace", foreign_key: :owner_id, conditions: 'type IS NULL', dependent: :destroy | |
| 49 | - has_many :groups, class_name: "Group", foreign_key: :owner_id | |
| 50 | - | |
| 51 | - has_many :keys, dependent: :destroy | |
| 52 | - has_many :users_projects, dependent: :destroy | |
| 53 | - has_many :issues, foreign_key: :author_id, dependent: :destroy | |
| 54 | - has_many :notes, foreign_key: :author_id, dependent: :destroy | |
| 55 | - has_many :merge_requests, foreign_key: :author_id, dependent: :destroy | |
| 56 | - has_many :events, class_name: "Event", foreign_key: :author_id, dependent: :destroy | |
| 57 | - has_many :recent_events, class_name: "Event", foreign_key: :author_id, order: "id DESC" | |
| 58 | - has_many :assigned_issues, class_name: "Issue", foreign_key: :assignee_id, dependent: :destroy | |
| 59 | - has_many :assigned_merge_requests, class_name: "MergeRequest", foreign_key: :assignee_id, dependent: :destroy | |
| 48 | + has_one :namespace, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace", conditions: 'type IS NULL' | |
| 49 | + | |
| 50 | + has_many :keys, dependent: :destroy | |
| 51 | + has_many :users_projects, dependent: :destroy | |
| 52 | + has_many :issues, dependent: :destroy, foreign_key: :author_id | |
| 53 | + has_many :notes, dependent: :destroy, foreign_key: :author_id | |
| 54 | + has_many :merge_requests, dependent: :destroy, foreign_key: :author_id | |
| 55 | + has_many :events, dependent: :destroy, foreign_key: :author_id, class_name: "Event" | |
| 56 | + has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue" | |
| 57 | + has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest" | |
| 58 | + | |
| 59 | + has_many :groups, class_name: "Group", foreign_key: :owner_id | |
| 60 | + has_many :recent_events, class_name: "Event", foreign_key: :author_id, order: "id DESC" | |
| 61 | + | |
| 62 | + has_many :projects, through: :users_projects | |
| 63 | + | |
| 64 | + has_many :user_team_user_relationships, dependent: :destroy | |
| 65 | + | |
| 66 | + has_many :user_teams, through: :user_team_user_relationships | |
| 67 | + has_many :user_team_project_relationships, through: :user_teams | |
| 68 | + has_many :team_projects, through: :user_team_project_relationships | |
| 60 | 69 | |
| 61 | 70 | validates :name, presence: true |
| 62 | 71 | validates :bio, length: { within: 0..255 } |
| ... | ... | @@ -80,6 +89,9 @@ class User < ActiveRecord::Base |
| 80 | 89 | scope :blocked, where(blocked: true) |
| 81 | 90 | scope :active, where(blocked: false) |
| 82 | 91 | scope :alphabetically, order('name ASC') |
| 92 | + scope :in_team, ->(team){ where(id: team.member_ids) } | |
| 93 | + scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) } | |
| 94 | + scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active } | |
| 83 | 95 | |
| 84 | 96 | # |
| 85 | 97 | # Class methods |
| ... | ... | @@ -131,6 +143,11 @@ class User < ActiveRecord::Base |
| 131 | 143 | # |
| 132 | 144 | # Instance methods |
| 133 | 145 | # |
| 146 | + | |
| 147 | + def to_param | |
| 148 | + username | |
| 149 | + end | |
| 150 | + | |
| 134 | 151 | def generate_password |
| 135 | 152 | if self.force_random_password |
| 136 | 153 | self.password = self.password_confirmation = Devise.friendly_token.first(8) |
| ... | ... | @@ -220,7 +237,7 @@ class User < ActiveRecord::Base |
| 220 | 237 | end |
| 221 | 238 | |
| 222 | 239 | def can_create_group? |
| 223 | - is_admin? | |
| 240 | + can?(:create_group, nil) | |
| 224 | 241 | end |
| 225 | 242 | |
| 226 | 243 | def abilities |
| ... | ... | @@ -283,4 +300,15 @@ class User < ActiveRecord::Base |
| 283 | 300 | def namespace_id |
| 284 | 301 | namespace.try :id |
| 285 | 302 | end |
| 303 | + | |
| 304 | + def authorized_teams | |
| 305 | + @authorized_teams ||= begin | |
| 306 | + ids = [] | |
| 307 | + ids << UserTeam.with_member(self).pluck('user_teams.id') | |
| 308 | + ids << UserTeam.created_by(self).pluck('user_teams.id') | |
| 309 | + ids.flatten | |
| 310 | + | |
| 311 | + UserTeam.where(id: ids) | |
| 312 | + end | |
| 313 | + end | |
| 286 | 314 | end | ... | ... |
| ... | ... | @@ -0,0 +1,97 @@ |
| 1 | +class UserTeam < ActiveRecord::Base | |
| 2 | + attr_accessible :name, :owner_id, :path | |
| 3 | + | |
| 4 | + belongs_to :owner, class_name: User | |
| 5 | + | |
| 6 | + has_many :user_team_project_relationships, dependent: :destroy | |
| 7 | + has_many :user_team_user_relationships, dependent: :destroy | |
| 8 | + | |
| 9 | + has_many :projects, through: :user_team_project_relationships | |
| 10 | + has_many :members, through: :user_team_user_relationships, source: :user | |
| 11 | + | |
| 12 | + validates :name, presence: true, uniqueness: true | |
| 13 | + validates :owner, presence: true | |
| 14 | + validates :path, uniqueness: true, presence: true, length: { within: 1..255 }, | |
| 15 | + format: { with: Gitlab::Regex.path_regex, | |
| 16 | + message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } | |
| 17 | + | |
| 18 | + scope :with_member, ->(user){ joins(:user_team_user_relationships).where(user_team_user_relationships: {user_id: user.id}) } | |
| 19 | + scope :with_project, ->(project){ joins(:user_team_project_relationships).where(user_team_project_relationships: {project_id: project})} | |
| 20 | + scope :without_project, ->(project){ where("user_teams.id NOT IN (:ids)", ids: (a = with_project(project); a.blank? ? 0 : a))} | |
| 21 | + scope :created_by, ->(user){ where(owner_id: user) } | |
| 22 | + | |
| 23 | + class << self | |
| 24 | + def search query | |
| 25 | + where("name LIKE :query OR path LIKE :query", query: "%#{query}%") | |
| 26 | + end | |
| 27 | + | |
| 28 | + def global_id | |
| 29 | + 'GLN' | |
| 30 | + end | |
| 31 | + | |
| 32 | + def access_roles | |
| 33 | + UsersProject.access_roles | |
| 34 | + end | |
| 35 | + end | |
| 36 | + | |
| 37 | + def to_param | |
| 38 | + path | |
| 39 | + end | |
| 40 | + | |
| 41 | + def assign_to_projects(projects, access) | |
| 42 | + projects.each do |project| | |
| 43 | + assign_to_project(project, access) | |
| 44 | + end | |
| 45 | + end | |
| 46 | + | |
| 47 | + def assign_to_project(project, access) | |
| 48 | + Gitlab::UserTeamManager.assign(self, project, access) | |
| 49 | + end | |
| 50 | + | |
| 51 | + def resign_from_project(project) | |
| 52 | + Gitlab::UserTeamManager.resign(self, project) | |
| 53 | + end | |
| 54 | + | |
| 55 | + def add_members(users, access, group_admin) | |
| 56 | + users.each do |user| | |
| 57 | + add_member(user, access, group_admin) | |
| 58 | + end | |
| 59 | + end | |
| 60 | + | |
| 61 | + def add_member(user, access, group_admin) | |
| 62 | + Gitlab::UserTeamManager.add_member_into_team(self, user, access, group_admin) | |
| 63 | + end | |
| 64 | + | |
| 65 | + def remove_member(user) | |
| 66 | + Gitlab::UserTeamManager.remove_member_from_team(self, user) | |
| 67 | + end | |
| 68 | + | |
| 69 | + def update_membership(user, options) | |
| 70 | + Gitlab::UserTeamManager.update_team_user_membership(self, user, options) | |
| 71 | + end | |
| 72 | + | |
| 73 | + def update_project_access(project, permission) | |
| 74 | + Gitlab::UserTeamManager.update_project_greates_access(self, project, permission) | |
| 75 | + end | |
| 76 | + | |
| 77 | + def max_project_access(project) | |
| 78 | + user_team_project_relationships.find_by_project_id(project).greatest_access | |
| 79 | + end | |
| 80 | + | |
| 81 | + def human_max_project_access(project) | |
| 82 | + self.class.access_roles.invert[max_project_access(project)] | |
| 83 | + end | |
| 84 | + | |
| 85 | + def default_projects_access(member) | |
| 86 | + user_team_user_relationships.find_by_user_id(member).permission | |
| 87 | + end | |
| 88 | + | |
| 89 | + def human_default_projects_access(member) | |
| 90 | + self.class.access_roles.invert[default_projects_access(member)] | |
| 91 | + end | |
| 92 | + | |
| 93 | + def admin?(member) | |
| 94 | + user_team_user_relationships.with_user(member).first.group_admin? | |
| 95 | + end | |
| 96 | + | |
| 97 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,28 @@ |
| 1 | +class UserTeamProjectRelationship < ActiveRecord::Base | |
| 2 | + attr_accessible :greatest_access, :project_id, :user_team_id | |
| 3 | + | |
| 4 | + belongs_to :user_team | |
| 5 | + belongs_to :project | |
| 6 | + | |
| 7 | + validates :project, presence: true | |
| 8 | + validates :user_team, presence: true | |
| 9 | + validate :check_greatest_access | |
| 10 | + | |
| 11 | + scope :with_project, ->(project){ where(project_id: project.id) } | |
| 12 | + | |
| 13 | + def team_name | |
| 14 | + user_team.name | |
| 15 | + end | |
| 16 | + | |
| 17 | + private | |
| 18 | + | |
| 19 | + def check_greatest_access | |
| 20 | + errors.add(:base, :incorrect_access_code) unless correct_access? | |
| 21 | + end | |
| 22 | + | |
| 23 | + def correct_access? | |
| 24 | + return false if greatest_access.blank? | |
| 25 | + return true if UsersProject.access_roles.has_value?(greatest_access) | |
| 26 | + false | |
| 27 | + end | |
| 28 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,19 @@ |
| 1 | +class UserTeamUserRelationship < ActiveRecord::Base | |
| 2 | + attr_accessible :group_admin, :permission, :user_id, :user_team_id | |
| 3 | + | |
| 4 | + belongs_to :user_team | |
| 5 | + belongs_to :user | |
| 6 | + | |
| 7 | + validates :user_team, presence: true | |
| 8 | + validates :user, presence: true | |
| 9 | + | |
| 10 | + scope :with_user, ->(user) { where(user_id: user.id) } | |
| 11 | + | |
| 12 | + def user_name | |
| 13 | + user.name | |
| 14 | + end | |
| 15 | + | |
| 16 | + def access_human | |
| 17 | + UsersProject.access_roles.invert[permission] | |
| 18 | + end | |
| 19 | +end | ... | ... |
app/models/users_project.rb
| ... | ... | @@ -39,7 +39,10 @@ class UsersProject < ActiveRecord::Base |
| 39 | 39 | scope :reporters, where(project_access: REPORTER) |
| 40 | 40 | scope :developers, where(project_access: DEVELOPER) |
| 41 | 41 | scope :masters, where(project_access: MASTER) |
| 42 | + | |
| 42 | 43 | scope :in_project, ->(project) { where(project_id: project.id) } |
| 44 | + scope :in_projects, ->(projects) { where(project_id: project_ids) } | |
| 45 | + scope :with_user, ->(user) { where(user_id: user.id) } | |
| 43 | 46 | |
| 44 | 47 | class << self |
| 45 | 48 | ... | ... |
app/models/web_hook.rb
app/views/admin/groups/show.html.haml
| ... | ... | @@ -72,16 +72,17 @@ |
| 72 | 72 | %th Users |
| 73 | 73 | %th Project Access: |
| 74 | 74 | |
| 75 | - - @group.users.each do |u| | |
| 76 | - %tr{class: "user_#{u.id}"} | |
| 77 | - %td.name= link_to u.name, admin_user_path(u) | |
| 75 | + - @group.users.each do |user| | |
| 76 | + - next unless user | |
| 77 | + %tr{class: "user_#{user.id}"} | |
| 78 | + %td.name= link_to user.name, admin_user_path(user) | |
| 78 | 79 | %td.projects_access |
| 79 | - - u.authorized_projects.in_namespace(@group).each do |project| | |
| 80 | - - u_p = u.users_projects.in_project(project).first | |
| 80 | + - user.authorized_projects.in_namespace(@group).each do |project| | |
| 81 | + - u_p = user.users_projects.in_project(project).first | |
| 81 | 82 | - next unless u_p |
| 82 | 83 | %span |
| 83 | - = project.name | |
| 84 | - = link_to "(#{ u_p.project_access_human })", edit_admin_team_member_path(u_p) | |
| 84 | + = project.name_with_namespace | |
| 85 | + = link_to "(#{ u_p.project_access_human })", edit_admin_project_member_path(project, user) | |
| 85 | 86 | %tr |
| 86 | 87 | %td.input= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5' |
| 87 | 88 | %td= select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3"} | ... | ... |
| ... | ... | @@ -0,0 +1,16 @@ |
| 1 | += form_for @team_member_relation, as: :team_member, url: admin_project_member_path(@project, @member) do |f| | |
| 2 | + -if @team_member_relation.errors.any? | |
| 3 | + .alert-message.block-message.error | |
| 4 | + %ul | |
| 5 | + - @team_member_relation.errors.full_messages.each do |msg| | |
| 6 | + %li= msg | |
| 7 | + | |
| 8 | + .clearfix | |
| 9 | + %label Project Access: | |
| 10 | + .input | |
| 11 | + = f.select :project_access, options_for_select(Project.access_options, @team_member_relation.project_access), {}, class: "project-access-select chosen span3" | |
| 12 | + | |
| 13 | + %br | |
| 14 | + .actions | |
| 15 | + = f.submit 'Save', class: "btn primary" | |
| 16 | + = link_to 'Cancel', :back, class: "btn" | ... | ... |
app/views/admin/projects/show.html.haml
| ... | ... | @@ -114,9 +114,9 @@ |
| 114 | 114 | %h5 |
| 115 | 115 | Team |
| 116 | 116 | %small |
| 117 | - (#{@project.users_projects.count}) | |
| 117 | + (#{@project.users.count}) | |
| 118 | 118 | %br |
| 119 | -%table.zebra-striped | |
| 119 | +%table.zebra-striped.team_members | |
| 120 | 120 | %thead |
| 121 | 121 | %tr |
| 122 | 122 | %th Name |
| ... | ... | @@ -124,13 +124,13 @@ |
| 124 | 124 | %th Repository Access |
| 125 | 125 | %th |
| 126 | 126 | |
| 127 | - - @project.users_projects.each do |tm| | |
| 127 | + - @project.users.each do |tm| | |
| 128 | 128 | %tr |
| 129 | 129 | %td |
| 130 | - = link_to tm.user_name, admin_user_path(tm.user) | |
| 131 | - %td= tm.project_access_human | |
| 132 | - %td= link_to 'Edit Access', edit_admin_team_member_path(tm), class: "btn small" | |
| 133 | - %td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn danger small" | |
| 130 | + = link_to tm.name, admin_user_path(tm) | |
| 131 | + %td= @project.project_access_human(tm) | |
| 132 | + %td= link_to 'Edit Access', edit_admin_project_member_path(@project, tm), class: "btn small" | |
| 133 | + %td= link_to 'Remove from team', admin_project_member_path(@project, tm), confirm: 'Are you sure?', method: :delete, class: "btn danger small" | |
| 134 | 134 | |
| 135 | 135 | %br |
| 136 | 136 | %h5 Add new team member | ... | ... |
app/views/admin/team_members/_form.html.haml
| ... | ... | @@ -1,16 +0,0 @@ |
| 1 | -= form_for @admin_team_member, as: :team_member, url: admin_team_member_path(@admin_team_member) do |f| | |
| 2 | - -if @admin_team_member.errors.any? | |
| 3 | - .alert-message.block-message.error | |
| 4 | - %ul | |
| 5 | - - @admin_team_member.errors.full_messages.each do |msg| | |
| 6 | - %li= msg | |
| 7 | - | |
| 8 | - .clearfix | |
| 9 | - %label Project Access: | |
| 10 | - .input | |
| 11 | - = f.select :project_access, options_for_select(Project.access_options, @admin_team_member.project_access), {}, class: "project-access-select chosen span3" | |
| 12 | - | |
| 13 | - %br | |
| 14 | - .actions | |
| 15 | - = f.submit 'Save', class: "btn primary" | |
| 16 | - = link_to 'Cancel', :back, class: "btn" |
app/views/admin/team_members/edit.html.haml
| ... | ... | @@ -0,0 +1,23 @@ |
| 1 | +%h3.page_title Rename Team | |
| 2 | +%hr | |
| 3 | += form_for @team, url: admin_team_path(@team), method: :put do |f| | |
| 4 | + - if @team.errors.any? | |
| 5 | + .alert-message.block-message.error | |
| 6 | + %span= @team.errors.full_messages.first | |
| 7 | + .clearfix.team_name_holder | |
| 8 | + = f.label :name do | |
| 9 | + Team name is | |
| 10 | + .input | |
| 11 | + = f.text_field :name, placeholder: "Example Team", class: "xxlarge" | |
| 12 | + | |
| 13 | + .clearfix.team_name_holder | |
| 14 | + = f.label :path do | |
| 15 | + %span.cred Team path is | |
| 16 | + .input | |
| 17 | + = f.text_field :path, placeholder: "example-team", class: "xxlarge danger" | |
| 18 | + %ul.cred | |
| 19 | + %li It will change web url for access team and team projects. | |
| 20 | + | |
| 21 | + .form-actions | |
| 22 | + = f.submit 'Rename team', class: "btn danger" | |
| 23 | + = link_to 'Cancel', admin_teams_path, class: "btn cancel-btn" | ... | ... |
| ... | ... | @@ -0,0 +1,38 @@ |
| 1 | +%h3.page_title | |
| 2 | + Teams | |
| 3 | + %small | |
| 4 | + simple Teams description | |
| 5 | + | |
| 6 | + = link_to 'New Team', new_admin_team_path, class: "btn small right" | |
| 7 | + %br | |
| 8 | + | |
| 9 | += form_tag admin_teams_path, method: :get, class: 'form-inline' do | |
| 10 | + = text_field_tag :name, params[:name], class: "xlarge" | |
| 11 | + = submit_tag "Search", class: "btn submit primary" | |
| 12 | + | |
| 13 | +%table | |
| 14 | + %thead | |
| 15 | + %tr | |
| 16 | + %th | |
| 17 | + Name | |
| 18 | + %i.icon-sort-down | |
| 19 | + %th Path | |
| 20 | + %th Projects | |
| 21 | + %th Members | |
| 22 | + %th Owner | |
| 23 | + %th.cred Danger Zone! | |
| 24 | + | |
| 25 | + - @teams.each do |team| | |
| 26 | + %tr | |
| 27 | + %td | |
| 28 | + %strong= link_to team.name, admin_team_path(team) | |
| 29 | + %td= team.path | |
| 30 | + %td= team.projects.count | |
| 31 | + %td= team.members.count | |
| 32 | + %td | |
| 33 | + = link_to team.owner.name, admin_user_path(team.owner_id) | |
| 34 | + %td.bgred | |
| 35 | + = link_to 'Rename', edit_admin_team_path(team), id: "edit_#{dom_id(team)}", class: "btn small" | |
| 36 | + = link_to 'Destroy', admin_team_path(team), confirm: "REMOVE #{team.name}? Are you sure?", method: :delete, class: "btn small danger" | |
| 37 | + | |
| 38 | += paginate @teams, theme: "admin" | ... | ... |
| ... | ... | @@ -0,0 +1,20 @@ |
| 1 | += form_tag admin_team_member_path(@team, @member), method: :put do | |
| 2 | + -if @member.errors.any? | |
| 3 | + .alert-message.block-message.error | |
| 4 | + %ul | |
| 5 | + - @member.errors.full_messages.each do |msg| | |
| 6 | + %li= msg | |
| 7 | + | |
| 8 | + .clearfix | |
| 9 | + %label Default access for Team projects: | |
| 10 | + .input | |
| 11 | + = select_tag :default_project_access, options_for_select(UserTeam.access_roles, @team.default_projects_access(@member)), class: "project-access-select chosen span3" | |
| 12 | + .clearfix | |
| 13 | + %label Team admin? | |
| 14 | + .input | |
| 15 | + = check_box_tag :group_admin, true, @team.admin?(@member) | |
| 16 | + | |
| 17 | + %br | |
| 18 | + .actions | |
| 19 | + = submit_tag 'Save', class: "btn primary" | |
| 20 | + = link_to 'Cancel', :back, class: "btn" | ... | ... |
| ... | ... | @@ -0,0 +1,16 @@ |
| 1 | +%h3 | |
| 2 | + Edit access #{@member.name} in #{@team.name} team | |
| 3 | + | |
| 4 | +%hr | |
| 5 | +%table.zebra-striped | |
| 6 | + %tr | |
| 7 | + %td User: | |
| 8 | + %td= @member.name | |
| 9 | + %tr | |
| 10 | + %td Team: | |
| 11 | + %td= @team.name | |
| 12 | + %tr | |
| 13 | + %td Since: | |
| 14 | + %td= member_since(@team, @member).stamp("Nov 11, 2010") | |
| 15 | + | |
| 16 | += render 'form' | ... | ... |
| ... | ... | @@ -0,0 +1,29 @@ |
| 1 | +%h3.page_title | |
| 2 | + Team: #{@team.name} | |
| 3 | + | |
| 4 | +%fieldset | |
| 5 | + %legend Members (#{@team.members.count}) | |
| 6 | + = form_tag admin_team_members_path(@team), id: "team_members", class: "bulk_import", method: :post do | |
| 7 | + %table#members_list | |
| 8 | + %thead | |
| 9 | + %tr | |
| 10 | + %th User name | |
| 11 | + %th Default project access | |
| 12 | + %th Team access | |
| 13 | + %th | |
| 14 | + - @team.members.each do |member| | |
| 15 | + %tr.member | |
| 16 | + %td | |
| 17 | + = link_to [:admin, member] do | |
| 18 | + = member.name | |
| 19 | + %small= "(#{member.email})" | |
| 20 | + %td= @team.human_default_projects_access(member) | |
| 21 | + %td= @team.admin?(member) ? "Admin" : "Member" | |
| 22 | + %td | |
| 23 | + %tr | |
| 24 | + %td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name_with_email), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5' | |
| 25 | + %td= select_tag :default_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" } | |
| 26 | + %td | |
| 27 | + %span= check_box_tag :group_admin | |
| 28 | + %span Admin? | |
| 29 | + %td= submit_tag 'Add', class: "btn primary", id: :add_members_to_team | ... | ... |
| ... | ... | @@ -0,0 +1,19 @@ |
| 1 | +%h3.page_title New Team | |
| 2 | +%hr | |
| 3 | += form_for @team, url: admin_teams_path do |f| | |
| 4 | + - if @team.errors.any? | |
| 5 | + .alert-message.block-message.error | |
| 6 | + %span= @team.errors.full_messages.first | |
| 7 | + .clearfix | |
| 8 | + = f.label :name do | |
| 9 | + Team name is | |
| 10 | + .input | |
| 11 | + = f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left" | |
| 12 | + | |
| 13 | + = f.submit 'Create team', class: "btn primary" | |
| 14 | + %hr | |
| 15 | + .padded | |
| 16 | + %ul | |
| 17 | + %li All created teams are public (users can view who enter into team and which project are assigned for this team) | |
| 18 | + %li People within a team see only projects they have access to | |
| 19 | + %li You will be able to assign existing projects for team | ... | ... |
| ... | ... | @@ -0,0 +1,16 @@ |
| 1 | += form_tag admin_team_project_path(@team, @project), method: :put do | |
| 2 | + -if @project.errors.any? | |
| 3 | + .alert-message.block-message.error | |
| 4 | + %ul | |
| 5 | + - @project.errors.full_messages.each do |msg| | |
| 6 | + %li= msg | |
| 7 | + | |
| 8 | + .clearfix | |
| 9 | + %label Max access for Team members: | |
| 10 | + .input | |
| 11 | + = select_tag :greatest_project_access, options_for_select(UserTeam.access_roles, @team.max_project_access(@project)), class: "project-access-select chosen span3" | |
| 12 | + | |
| 13 | + %br | |
| 14 | + .actions | |
| 15 | + = submit_tag 'Save', class: "btn primary" | |
| 16 | + = link_to 'Cancel', :back, class: "btn" | ... | ... |
| ... | ... | @@ -0,0 +1,16 @@ |
| 1 | +%h3 | |
| 2 | + Edit max access in #{@project.name} for #{@team.name} team | |
| 3 | + | |
| 4 | +%hr | |
| 5 | +%table.zebra-striped | |
| 6 | + %tr | |
| 7 | + %td Project: | |
| 8 | + %td= @project.name | |
| 9 | + %tr | |
| 10 | + %td Team: | |
| 11 | + %td= @team.name | |
| 12 | + %tr | |
| 13 | + %td Since: | |
| 14 | + %td= assigned_since(@team, @project).stamp("Nov 11, 2010") | |
| 15 | + | |
| 16 | += render 'form' | ... | ... |
| ... | ... | @@ -0,0 +1,23 @@ |
| 1 | +%h3.page_title | |
| 2 | + Team: #{@team.name} | |
| 3 | + | |
| 4 | +%fieldset | |
| 5 | + %legend Projects (#{@team.projects.count}) | |
| 6 | + = form_tag admin_team_projects_path(@team), id: "assign_projects", class: "bulk_import", method: :post do | |
| 7 | + %table#projects_list | |
| 8 | + %thead | |
| 9 | + %tr | |
| 10 | + %th Project name | |
| 11 | + %th Max access | |
| 12 | + %th | |
| 13 | + - @team.projects.each do |project| | |
| 14 | + %tr.project | |
| 15 | + %td | |
| 16 | + = link_to project.name_with_namespace, [:admin, project] | |
| 17 | + %td | |
| 18 | + %span= @team.human_max_project_access(project) | |
| 19 | + %td | |
| 20 | + %tr | |
| 21 | + %td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5' | |
| 22 | + %td= select_tag :greatest_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" } | |
| 23 | + %td= submit_tag 'Add', class: "btn primary", id: :assign_projects_to_team | ... | ... |
| ... | ... | @@ -0,0 +1,101 @@ |
| 1 | +%h3.page_title | |
| 2 | + Team: #{@team.name} | |
| 3 | + | |
| 4 | +%br | |
| 5 | +%table.zebra-striped | |
| 6 | + %thead | |
| 7 | + %tr | |
| 8 | + %th Team | |
| 9 | + %th | |
| 10 | + %tr | |
| 11 | + %td | |
| 12 | + %b | |
| 13 | + Name: | |
| 14 | + %td | |
| 15 | + = @team.name | |
| 16 | + | |
| 17 | + = link_to edit_admin_team_path(@team), class: "btn btn-small right" do | |
| 18 | + %i.icon-edit | |
| 19 | + Rename | |
| 20 | + %tr | |
| 21 | + %td | |
| 22 | + %b | |
| 23 | + Owner: | |
| 24 | + %td | |
| 25 | + = @team.owner.name | |
| 26 | + .right | |
| 27 | + = link_to "#", class: "btn btn-small change-owner-link" do | |
| 28 | + %i.icon-edit | |
| 29 | + Change owner | |
| 30 | + | |
| 31 | + %tr.change-owner-holder.hide | |
| 32 | + %td.bgred | |
| 33 | + %b.cred | |
| 34 | + New Owner: | |
| 35 | + %td.bgred | |
| 36 | + = form_for @team, url: admin_team_path(@team) do |f| | |
| 37 | + = f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'} | |
| 38 | + %div | |
| 39 | + = f.submit 'Change Owner', class: "btn danger" | |
| 40 | + = link_to "Cancel", "#", class: "btn change-owner-cancel-link" | |
| 41 | + | |
| 42 | +%fieldset | |
| 43 | + %legend | |
| 44 | + Members (#{@team.members.count}) | |
| 45 | + %span= link_to 'Add members', new_admin_team_member_path(@team), class: "btn success small right", id: :add_members_to_team | |
| 46 | + - if @team.members.any? | |
| 47 | + %table#members_list | |
| 48 | + %thead | |
| 49 | + %tr | |
| 50 | + %th User name | |
| 51 | + %th Default project access | |
| 52 | + %th Team access | |
| 53 | + %th.cred.span3 Danger Zone! | |
| 54 | + - @team.members.each do |member| | |
| 55 | + %tr.member{ class: "user_#{member.id}"} | |
| 56 | + %td | |
| 57 | + = link_to [:admin, member] do | |
| 58 | + = member.name | |
| 59 | + %small= "(#{member.email})" | |
| 60 | + %td= @team.human_default_projects_access(member) | |
| 61 | + %td= @team.admin?(member) ? "Admin" : "Member" | |
| 62 | + %td.bgred | |
| 63 | + = link_to 'Edit', edit_admin_team_member_path(@team, member), class: "btn small" | |
| 64 | + | |
| 65 | + = link_to 'Remove', admin_team_member_path(@team, member), confirm: 'Remove member from team. Are you sure?', method: :delete, class: "btn danger small", id: "remove_member_#{member.id}" | |
| 66 | + | |
| 67 | +%fieldset | |
| 68 | + %legend | |
| 69 | + Projects (#{@team.projects.count}) | |
| 70 | + %span= link_to 'Add projects', new_admin_team_project_path(@team), class: "btn success small right", id: :assign_projects_to_team | |
| 71 | + - if @team.projects.any? | |
| 72 | + %table#projects_list | |
| 73 | + %thead | |
| 74 | + %tr | |
| 75 | + %th Project name | |
| 76 | + %th Max access | |
| 77 | + %th.cred.span3 Danger Zone! | |
| 78 | + - @team.projects.each do |project| | |
| 79 | + %tr.project | |
| 80 | + %td | |
| 81 | + = link_to project.name_with_namespace, [:admin, project] | |
| 82 | + %td | |
| 83 | + %span= @team.human_max_project_access(project) | |
| 84 | + %td.bgred | |
| 85 | + = link_to 'Edit', edit_admin_team_project_path(@team, project), class: "btn small" | |
| 86 | + | |
| 87 | + = link_to 'Relegate', admin_team_project_path(@team, project), confirm: 'Remove project from team. Are you sure?', method: :delete, class: "btn danger small", id: "relegate_project_#{project.id}" | |
| 88 | + | |
| 89 | +:javascript | |
| 90 | + $(function(){ | |
| 91 | + var modal = $('.change-owner-holder'); | |
| 92 | + $('.change-owner-link').bind("click", function(){ | |
| 93 | + $(this).hide(); | |
| 94 | + modal.show(); | |
| 95 | + }); | |
| 96 | + $('.change-owner-cancel-link').bind("click", function(){ | |
| 97 | + modal.hide(); | |
| 98 | + $('.change-owner-link').show(); | |
| 99 | + }) | |
| 100 | + }) | |
| 101 | + | ... | ... |
app/views/admin/users/_form.html.haml
| ... | ... | @@ -47,6 +47,14 @@ |
| 47 | 47 | .input= f.number_field :projects_limit |
| 48 | 48 | |
| 49 | 49 | .clearfix |
| 50 | + = f.label :can_create_group | |
| 51 | + .input= f.check_box :can_create_group | |
| 52 | + | |
| 53 | + .clearfix | |
| 54 | + = f.label :can_create_team | |
| 55 | + .input= f.check_box :can_create_team | |
| 56 | + | |
| 57 | + .clearfix | |
| 50 | 58 | = f.label :admin do |
| 51 | 59 | %strong.cred Administrator |
| 52 | 60 | .input= f.check_box :admin | ... | ... |
app/views/admin/users/show.html.haml
| ... | ... | @@ -123,5 +123,5 @@ |
| 123 | 123 | %tr |
| 124 | 124 | %td= link_to project.name_with_namespace, admin_project_path(project) |
| 125 | 125 | %td= tm.project_access_human |
| 126 | - %td= link_to 'Edit Access', edit_admin_team_member_path(tm), class: "btn small" | |
| 127 | - %td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn small danger" | |
| 126 | + %td= link_to 'Edit Access', edit_admin_project_member_path(project, tm.user), class: "btn small" | |
| 127 | + %td= link_to 'Remove from team', admin_project_member_path(project, tm.user), confirm: 'Are you sure?', method: :delete, class: "btn small danger" | ... | ... |
app/views/commit/show.html.haml
| ... | ... | @@ -11,19 +11,7 @@ |
| 11 | 11 | |
| 12 | 12 | :javascript |
| 13 | 13 | $(function(){ |
| 14 | - var w, h; | |
| 15 | - $('.diff_file').each(function(){ | |
| 16 | - $('.image.diff_removed img', this).on('load', $.proxy(function(event){ | |
| 17 | - var w = event.currentTarget.naturalWidth | |
| 18 | - , h = event.currentTarget.naturalHeight; | |
| 19 | - $('.image.diff_removed .image-info', this).append(' | <b>W:</b> ' + w + 'px | <b>H:</b> ' + h + 'px'); | |
| 20 | - }, this)); | |
| 21 | - $('.image.diff_added img', this).on('load', $.proxy(function(event){ | |
| 22 | - var w = event.currentTarget.naturalWidth | |
| 23 | - , h = event.currentTarget.naturalHeight; | |
| 24 | - $('.image.diff_added .image-info', this).append(' | <b>W:</b> ' + w + 'px | <b>H:</b> ' + h + 'px'); | |
| 25 | - }, this)); | |
| 26 | - | |
| 14 | + $('.files .file').each(function(){ | |
| 15 | + new CommitFile(this); | |
| 27 | 16 | }); |
| 28 | - | |
| 29 | 17 | }); | ... | ... |
app/views/commits/_commits.html.haml
app/views/commits/_diffs.html.haml
| ... | ... | @@ -12,50 +12,38 @@ |
| 12 | 12 | .file-stats |
| 13 | 13 | = render "commits/diff_head", diffs: diffs |
| 14 | 14 | |
| 15 | -- unless @suppress_diff | |
| 16 | - - diffs.each_with_index do |diff, i| | |
| 17 | - - next if diff.diff.empty? | |
| 18 | - - file = (@commit.tree / diff.new_path) | |
| 19 | - - file = (@commit.prev_commit.tree / diff.old_path) unless file | |
| 20 | - - next unless file | |
| 21 | - .diff_file{id: "diff-#{i}"} | |
| 22 | - .diff_file_header | |
| 23 | - - if diff.deleted_file | |
| 24 | - %span= diff.old_path | |
| 15 | +.files | |
| 16 | + - unless @suppress_diff | |
| 17 | + - diffs.each_with_index do |diff, i| | |
| 18 | + - next if diff.diff.empty? | |
| 19 | + - file = (@commit.tree / diff.new_path) | |
| 20 | + - file = (@commit.prev_commit.tree / diff.old_path) unless file | |
| 21 | + - next unless file | |
| 22 | + .file{id: "diff-#{i}"} | |
| 23 | + .header | |
| 24 | + - if diff.deleted_file | |
| 25 | + %span= diff.old_path | |
| 25 | 26 | |
| 26 | - - if @commit.prev_commit | |
| 27 | - = link_to project_tree_path(@project, tree_join(@commit.prev_commit_id, diff.new_path)), {:class => 'btn right view-commit'} do | |
| 27 | + - if @commit.prev_commit | |
| 28 | + = link_to project_tree_path(@project, tree_join(@commit.prev_commit_id, diff.new_path)), {:class => 'btn right view-file'} do | |
| 29 | + View file @ | |
| 30 | + %span.commit-short-id= @commit.short_id(6) | |
| 31 | + - else | |
| 32 | + %span= diff.new_path | |
| 33 | + - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode | |
| 34 | + %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}" | |
| 35 | + | |
| 36 | + = link_to project_tree_path(@project, tree_join(@commit.id, diff.new_path)), {:class => 'btn very_small right view-file'} do | |
| 28 | 37 | View file @ |
| 29 | 38 | %span.commit-short-id= @commit.short_id(6) |
| 30 | - - else | |
| 31 | - %span= diff.new_path | |
| 32 | - - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode | |
| 33 | - %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}" | |
| 34 | - | |
| 35 | - = link_to project_tree_path(@project, tree_join(@commit.id, diff.new_path)), {:class => 'btn very_small right view-commit'} do | |
| 36 | - View file @ | |
| 37 | - %span.commit-short-id= @commit.short_id(6) | |
| 38 | 39 | |
| 39 | - %br/ | |
| 40 | - .diff_file_content | |
| 41 | - -# Skip all non-supported blobs | |
| 42 | - - next unless file.respond_to?('text?') | |
| 43 | - - if file.text? | |
| 44 | - = render "commits/text_diff", diff: diff, index: i | |
| 45 | - - elsif file.image? | |
| 46 | - - old_file = (@commit.prev_commit.tree / diff.old_path) if !@commit.prev_commit.nil? | |
| 47 | - - if diff.renamed_file || diff.new_file || diff.deleted_file | |
| 48 | - .diff_file_content_image | |
| 49 | - .image{class: image_diff_class(diff)} | |
| 50 | - %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} | |
| 51 | - %div.image-info= "#{number_to_human_size file.size}" | |
| 40 | + .content | |
| 41 | + -# Skipp all non non-supported blobs | |
| 42 | + - next unless file.respond_to?('text?') | |
| 43 | + - if file.text? | |
| 44 | + = render "commits/text_file", diff: diff, index: i | |
| 45 | + - elsif file.image? | |
| 46 | + - old_file = (@commit.prev_commit.tree / diff.old_path) if !@commit.prev_commit.nil? | |
| 47 | + = render "commits/image", diff: diff, old_file: old_file, file: file, index: i | |
| 52 | 48 | - else |
| 53 | - .diff_file_content_image.img_compared | |
| 54 | - .image.diff_removed | |
| 55 | - %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(old_file.data)}"} | |
| 56 | - %div.image-info= "#{number_to_human_size file.size}" | |
| 57 | - .image.diff_added | |
| 58 | - %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} | |
| 59 | - %div.image-info= "#{number_to_human_size file.size}" | |
| 60 | - - else | |
| 61 | - %p.nothing_here_message No preview for this file type | |
| 49 | + %p.nothing_here_message No preview for this file type | ... | ... |
| ... | ... | @@ -0,0 +1,63 @@ |
| 1 | +- if diff.renamed_file || diff.new_file || diff.deleted_file | |
| 2 | + .image | |
| 3 | + %span.wrap | |
| 4 | + .frame{class: image_diff_class(diff)} | |
| 5 | + %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} | |
| 6 | + %p.image-info= "#{number_to_human_size file.size}" | |
| 7 | +- else | |
| 8 | + .image | |
| 9 | + %div.two-up.view | |
| 10 | + %span.wrap | |
| 11 | + .frame.deleted | |
| 12 | + %a{href: project_tree_path(@project, tree_join(@commit.id, diff.old_path))} | |
| 13 | + %img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"} | |
| 14 | + %p.image-info.hide | |
| 15 | + %span.meta-filesize= "#{number_to_human_size old_file.size}" | |
| 16 | + | | |
| 17 | + %b W: | |
| 18 | + %span.meta-width | |
| 19 | + | | |
| 20 | + %b H: | |
| 21 | + %span.meta-height | |
| 22 | + %span.wrap | |
| 23 | + .frame.added | |
| 24 | + %a{href: project_tree_path(@project, tree_join(@commit.id, diff.new_path))} | |
| 25 | + %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} | |
| 26 | + %p.image-info.hide | |
| 27 | + %span.meta-filesize= "#{number_to_human_size file.size}" | |
| 28 | + | | |
| 29 | + %b W: | |
| 30 | + %span.meta-width | |
| 31 | + | | |
| 32 | + %b H: | |
| 33 | + %span.meta-height | |
| 34 | + | |
| 35 | + %div.swipe.view.hide | |
| 36 | + .swipe-frame | |
| 37 | + .frame.deleted | |
| 38 | + %img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"} | |
| 39 | + .swipe-wrap | |
| 40 | + .frame.added | |
| 41 | + %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} | |
| 42 | + %span.swipe-bar | |
| 43 | + %span.top-handle | |
| 44 | + %span.bottom-handle | |
| 45 | + | |
| 46 | + %div.onion-skin.view.hide | |
| 47 | + .onion-skin-frame | |
| 48 | + .frame.deleted | |
| 49 | + %img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"} | |
| 50 | + .frame.added | |
| 51 | + %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} | |
| 52 | + .controls | |
| 53 | + .transparent | |
| 54 | + .drag-track | |
| 55 | + .dragger{:style => "left: 0px;"} | |
| 56 | + .opaque | |
| 57 | + | |
| 58 | + | |
| 59 | + .view-modes.hide | |
| 60 | + %ul.view-modes-menu | |
| 61 | + %li.two-up{data: {mode: 'two-up'}} 2-up | |
| 62 | + %li.swipe{data: {mode: 'swipe'}} Swipe | |
| 63 | + %li.onion-skin{data: {mode: 'onion-skin'}} Onion skin | |
| 0 | 64 | \ No newline at end of file | ... | ... |
app/views/commits/_text_diff.html.haml
| ... | ... | @@ -1,23 +0,0 @@ |
| 1 | -- too_big = diff.diff.lines.count > 1000 | |
| 2 | -- if too_big | |
| 3 | - %a.supp_diff_link Diff suppressed. Click to show | |
| 4 | - | |
| 5 | -%table{class: "#{'hide' if too_big}"} | |
| 6 | - - each_diff_line(diff, index) do |line, type, line_code, line_new, line_old| | |
| 7 | - %tr.line_holder{ id: line_code } | |
| 8 | - - if type == "match" | |
| 9 | - %td.old_line= "..." | |
| 10 | - %td.new_line= "..." | |
| 11 | - %td.line_content.matched= line | |
| 12 | - - else | |
| 13 | - %td.old_line | |
| 14 | - = link_to raw(type == "new" ? " " : line_old), "##{line_code}", id: line_code | |
| 15 | - - if @comments_allowed | |
| 16 | - = render "notes/diff_note_link", line_code: line_code | |
| 17 | - %td.new_line= link_to raw(type == "old" ? " " : line_new) , "##{line_code}", id: line_code | |
| 18 | - %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line) | |
| 19 | - | |
| 20 | - - if @reply_allowed | |
| 21 | - - comments = @line_notes.select { |n| n.line_code == line_code }.sort_by(&:created_at) | |
| 22 | - - unless comments.empty? | |
| 23 | - = render "notes/diff_notes_with_reply", notes: comments |
| ... | ... | @@ -0,0 +1,23 @@ |
| 1 | +- too_big = diff.diff.lines.count > 1000 | |
| 2 | +- if too_big | |
| 3 | + %a.supp_diff_link Diff suppressed. Click to show | |
| 4 | + | |
| 5 | +%table.text-file{class: "#{'hide' if too_big}"} | |
| 6 | + - each_diff_line(diff, index) do |line, type, line_code, line_new, line_old| | |
| 7 | + %tr.line_holder{ id: line_code } | |
| 8 | + - if type == "match" | |
| 9 | + %td.old_line= "..." | |
| 10 | + %td.new_line= "..." | |
| 11 | + %td.line_content.matched= line | |
| 12 | + - else | |
| 13 | + %td.old_line | |
| 14 | + = link_to raw(type == "new" ? " " : line_old), "##{line_code}", id: line_code | |
| 15 | + - if @comments_allowed | |
| 16 | + = render "notes/diff_note_link", line_code: line_code | |
| 17 | + %td.new_line= link_to raw(type == "old" ? " " : line_new) , "##{line_code}", id: line_code | |
| 18 | + %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line) | |
| 19 | + | |
| 20 | + - if @reply_allowed | |
| 21 | + - comments = @line_notes.select { |n| n.line_code == line_code }.sort_by(&:created_at) | |
| 22 | + - unless comments.empty? | |
| 23 | + = render "notes/diff_notes_with_reply", notes: comments | ... | ... |
app/views/commits/show.html.haml
app/views/dashboard/_groups.html.haml
| 1 | -.groups_box | |
| 1 | +.ui-box | |
| 2 | 2 | %h5.title |
| 3 | 3 | Groups |
| 4 | 4 | %small |
| 5 | 5 | (#{groups.count}) |
| 6 | 6 | - if current_user.can_create_group? |
| 7 | 7 | %span.right |
| 8 | - = link_to new_admin_group_path, class: "btn very_small info" do | |
| 8 | + = link_to new_group_path, class: "btn very_small info" do | |
| 9 | 9 | %i.icon-plus |
| 10 | 10 | New Group |
| 11 | 11 | %ul.well-list |
| ... | ... | @@ -13,8 +13,6 @@ |
| 13 | 13 | %li |
| 14 | 14 | = link_to group_path(id: group.path), class: dom_class(group) do |
| 15 | 15 | %strong.well-title= truncate(group.name, length: 35) |
| 16 | - %span.arrow | |
| 17 | - → | |
| 18 | - %span.last_activity | |
| 19 | - %strong Projects: | |
| 20 | - %span= current_user.authorized_projects.where(namespace_id: group.id).count | |
| 16 | + %span.right.light | |
| 17 | + - if group.owner == current_user | |
| 18 | + %i.icon-wrench | ... | ... |
app/views/dashboard/_projects.html.haml
| ... | ... | @@ -2,19 +2,12 @@ |
| 2 | 2 | %h5.title |
| 3 | 3 | Projects |
| 4 | 4 | %small |
| 5 | - (#{projects.total_count}) | |
| 5 | + (#{@projects_count}) | |
| 6 | 6 | - if current_user.can_create_project? |
| 7 | 7 | %span.right |
| 8 | 8 | = link_to new_project_path, class: "btn very_small info" do |
| 9 | 9 | %i.icon-plus |
| 10 | 10 | New Project |
| 11 | - %ul.nav.nav-projects-tabs | |
| 12 | - = nav_tab :scope, nil do | |
| 13 | - = link_to "All", dashboard_path | |
| 14 | - = nav_tab :scope, 'personal' do | |
| 15 | - = link_to "Personal", dashboard_path(scope: 'personal') | |
| 16 | - = nav_tab :scope, 'joined' do | |
| 17 | - = link_to "Joined", dashboard_path(scope: 'joined') | |
| 18 | 11 | |
| 19 | 12 | %ul.well-list |
| 20 | 13 | - projects.each do |project| |
| ... | ... | @@ -33,4 +26,6 @@ |
| 33 | 26 | - if projects.blank? |
| 34 | 27 | %li |
| 35 | 28 | %h3.nothing_here_message There are no projects here. |
| 36 | - .bottom= paginate projects, theme: "gitlab" | |
| 29 | + - if @projects_count > 20 | |
| 30 | + %li.bottom | |
| 31 | + %strong= link_to "show all projects", projects_dashboard_path | ... | ... |
app/views/dashboard/_sidebar.html.haml
| ... | ... | @@ -0,0 +1,20 @@ |
| 1 | +.ui-box.teams-box | |
| 2 | + %h5.title | |
| 3 | + Teams | |
| 4 | + %small | |
| 5 | + (#{@teams.count}) | |
| 6 | + %span.right | |
| 7 | + = link_to new_team_path, class: "btn very_small info" do | |
| 8 | + %i.icon-plus | |
| 9 | + New Team | |
| 10 | + %ul.well-list | |
| 11 | + - @teams.each do |team| | |
| 12 | + %li | |
| 13 | + = link_to team_path(id: team.path), class: dom_class(team) do | |
| 14 | + %strong.well-title= truncate(team.name, length: 35) | |
| 15 | + %span.right.light | |
| 16 | + - if team.owner == current_user | |
| 17 | + %i.icon-wrench | |
| 18 | + - tm = current_user.user_team_user_relationships.find_by_user_team_id(team.id) | |
| 19 | + - if tm | |
| 20 | + = tm.access_human | ... | ... |
app/views/dashboard/index.atom.builder
| ... | ... | @@ -1,30 +0,0 @@ |
| 1 | -xml.instruct! | |
| 2 | -xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do | |
| 3 | - xml.title "Dashboard feed#{" - #{current_user.name}" if current_user.name.present?}" | |
| 4 | - xml.link :href => projects_url(:atom), :rel => "self", :type => "application/atom+xml" | |
| 5 | - xml.link :href => projects_url, :rel => "alternate", :type => "text/html" | |
| 6 | - xml.id projects_url | |
| 7 | - xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? | |
| 8 | - | |
| 9 | - @events.each do |event| | |
| 10 | - if event.proper? | |
| 11 | - event = EventDecorator.decorate(event) | |
| 12 | - xml.entry do | |
| 13 | - event_link = event.feed_url | |
| 14 | - event_title = event.feed_title | |
| 15 | - event_summary = event.feed_summary | |
| 16 | - | |
| 17 | - xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}" | |
| 18 | - xml.link :href => event_link | |
| 19 | - xml.title truncate(event_title, :length => 80) | |
| 20 | - xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") | |
| 21 | - xml.media :thumbnail, :width => "40", :height => "40", :url => gravatar_icon(event.author_email) | |
| 22 | - xml.author do |author| | |
| 23 | - xml.name event.author_name | |
| 24 | - xml.email event.author_email | |
| 25 | - end | |
| 26 | - xml.summary(:type => "xhtml") { |x| x << event_summary unless event_summary.nil? } | |
| 27 | - end | |
| 28 | - end | |
| 29 | - end | |
| 30 | -end |
app/views/dashboard/index.html.haml
app/views/dashboard/index.js.haml