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 @@ | @@ -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 | \ No newline at end of file | 129 | \ No newline at end of file |
app/assets/javascripts/commits.js
@@ -1,59 +0,0 @@ | @@ -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 @@ | @@ -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 | \ No newline at end of file | 55 | \ No newline at end of file |
app/assets/javascripts/dashboard.js.coffee
@@ -4,11 +4,11 @@ window.dashboardPage = -> | @@ -4,11 +4,11 @@ window.dashboardPage = -> | ||
4 | event.preventDefault() | 4 | event.preventDefault() |
5 | toggleFilter $(this) | 5 | toggleFilter $(this) |
6 | reloadActivities() | 6 | reloadActivities() |
7 | - | 7 | + |
8 | reloadActivities = -> | 8 | reloadActivities = -> |
9 | $(".content_list").html '' | 9 | $(".content_list").html '' |
10 | Pager.init 20, true | 10 | Pager.init 20, true |
11 | - | 11 | + |
12 | toggleFilter = (sender) -> | 12 | toggleFilter = (sender) -> |
13 | sender.parent().toggleClass "inactive" | 13 | sender.parent().toggleClass "inactive" |
14 | event_filters = $.cookie("event_filter") | 14 | event_filters = $.cookie("event_filter") |
@@ -17,11 +17,11 @@ toggleFilter = (sender) -> | @@ -17,11 +17,11 @@ toggleFilter = (sender) -> | ||
17 | event_filters = event_filters.split(",") | 17 | event_filters = event_filters.split(",") |
18 | else | 18 | else |
19 | event_filters = new Array() | 19 | event_filters = new Array() |
20 | - | 20 | + |
21 | index = event_filters.indexOf(filter) | 21 | index = event_filters.indexOf(filter) |
22 | if index is -1 | 22 | if index is -1 |
23 | event_filters.push filter | 23 | event_filters.push filter |
24 | else | 24 | else |
25 | event_filters.splice index, 1 | 25 | event_filters.splice index, 1 |
26 | - | 26 | + |
27 | $.cookie "event_filter", event_filters.join(",") | 27 | $.cookie "event_filter", event_filters.join(",") |
app/assets/javascripts/merge_requests.js.coffee
1 | # | 1 | # |
2 | # * Filter merge requests | 2 | # * Filter merge requests |
3 | -# | 3 | +# |
4 | @merge_requestsPage = -> | 4 | @merge_requestsPage = -> |
5 | $('#assignee_id').chosen() | 5 | $('#assignee_id').chosen() |
6 | $('#milestone_id').chosen() | 6 | $('#milestone_id').chosen() |
@@ -8,16 +8,16 @@ | @@ -8,16 +8,16 @@ | ||
8 | $(this).closest('form').submit() | 8 | $(this).closest('form').submit() |
9 | 9 | ||
10 | class MergeRequest | 10 | class MergeRequest |
11 | - | 11 | + |
12 | constructor: (@opts) -> | 12 | constructor: (@opts) -> |
13 | this.$el = $('.merge-request') | 13 | this.$el = $('.merge-request') |
14 | @diffs_loaded = false | 14 | @diffs_loaded = false |
15 | @commits_loaded = false | 15 | @commits_loaded = false |
16 | - | 16 | + |
17 | this.activateTab(@opts.action) | 17 | this.activateTab(@opts.action) |
18 | - | 18 | + |
19 | this.bindEvents() | 19 | this.bindEvents() |
20 | - | 20 | + |
21 | this.initMergeWidget() | 21 | this.initMergeWidget() |
22 | this.$('.show-all-commits').on 'click', => | 22 | this.$('.show-all-commits').on 'click', => |
23 | this.showAllCommits() | 23 | this.showAllCommits() |
@@ -28,7 +28,7 @@ class MergeRequest | @@ -28,7 +28,7 @@ class MergeRequest | ||
28 | 28 | ||
29 | initMergeWidget: -> | 29 | initMergeWidget: -> |
30 | this.showState( @opts.current_state ) | 30 | this.showState( @opts.current_state ) |
31 | - | 31 | + |
32 | if this.$('.automerge_widget').length and @opts.check_enable | 32 | if this.$('.automerge_widget').length and @opts.check_enable |
33 | $.get @opts.url_to_automerge_check, (data) => | 33 | $.get @opts.url_to_automerge_check, (data) => |
34 | this.showState( data.state ) | 34 | this.showState( data.state ) |
@@ -42,12 +42,12 @@ class MergeRequest | @@ -42,12 +42,12 @@ class MergeRequest | ||
42 | bindEvents: -> | 42 | bindEvents: -> |
43 | this.$('.nav-tabs').on 'click', 'a', (event) => | 43 | this.$('.nav-tabs').on 'click', 'a', (event) => |
44 | a = $(event.currentTarget) | 44 | a = $(event.currentTarget) |
45 | - | 45 | + |
46 | href = a.attr('href') | 46 | href = a.attr('href') |
47 | History.replaceState {path: href}, document.title, href | 47 | History.replaceState {path: href}, document.title, href |
48 | - | 48 | + |
49 | event.preventDefault() | 49 | event.preventDefault() |
50 | - | 50 | + |
51 | this.$('.nav-tabs').on 'click', 'li', (event) => | 51 | this.$('.nav-tabs').on 'click', 'li', (event) => |
52 | this.activateTab($(event.currentTarget).data('action')) | 52 | this.activateTab($(event.currentTarget).data('action')) |
53 | 53 |
app/assets/javascripts/pager.js
@@ -1,56 +0,0 @@ | @@ -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 @@ | @@ -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 | html { | 1 | html { |
2 | - overflow-y: scroll; | 2 | + overflow-y: scroll; |
3 | } | 3 | } |
4 | 4 | ||
5 | /** LAYOUT **/ | 5 | /** LAYOUT **/ |
@@ -277,8 +277,20 @@ p.time { | @@ -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 | .highlight_word { | 292 | .highlight_word { |
281 | - background: #EEDC94; | 293 | + border-bottom: 2px solid #F90; |
282 | } | 294 | } |
283 | 295 | ||
284 | .status_info { | 296 | .status_info { |
@@ -326,7 +338,7 @@ li.note { | @@ -326,7 +338,7 @@ li.note { | ||
326 | li { | 338 | li { |
327 | border-bottom:none !important; | 339 | border-bottom:none !important; |
328 | } | 340 | } |
329 | - .file { | 341 | + .attachment { |
330 | padding-left: 20px; | 342 | padding-left: 20px; |
331 | background:url("icon-attachment.png") no-repeat left center; | 343 | background:url("icon-attachment.png") no-repeat left center; |
332 | } | 344 | } |
app/assets/stylesheets/gitlab_bootstrap/files.scss
@@ -135,7 +135,7 @@ | @@ -135,7 +135,7 @@ | ||
135 | pre { | 135 | pre { |
136 | border: none; | 136 | border: none; |
137 | border-radius: 0; | 137 | border-radius: 0; |
138 | - font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; | 138 | + font-family: $monospace_font; |
139 | font-size: 12px !important; | 139 | font-size: 12px !important; |
140 | line-height: 16px !important; | 140 | line-height: 16px !important; |
141 | margin: 0; | 141 | margin: 0; |
app/assets/stylesheets/gitlab_bootstrap/fonts.scss
@@ -4,4 +4,4 @@ | @@ -4,4 +4,4 @@ | ||
4 | } | 4 | } |
5 | 5 | ||
6 | /** Typo **/ | 6 | /** Typo **/ |
7 | -$monospace: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace; | 7 | +$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace; |
app/assets/stylesheets/gitlab_bootstrap/typography.scss
@@ -21,7 +21,7 @@ h6 { | @@ -21,7 +21,7 @@ h6 { | ||
21 | 21 | ||
22 | /** CODE **/ | 22 | /** CODE **/ |
23 | pre { | 23 | pre { |
24 | - font-family:'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; | 24 | + font-family: $monospace_font; |
25 | 25 | ||
26 | &.dark { | 26 | &.dark { |
27 | background: #333; | 27 | background: #333; |
@@ -79,7 +79,7 @@ a:focus { | @@ -79,7 +79,7 @@ a:focus { | ||
79 | } | 79 | } |
80 | 80 | ||
81 | .monospace { | 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
1 | -/** Colors **/ | 1 | +/** |
2 | + * General Colors | ||
3 | + */ | ||
2 | $primary_color: #2FA0BB; | 4 | $primary_color: #2FA0BB; |
3 | $link_color: #3A89A3; | 5 | $link_color: #3A89A3; |
4 | $style_color: #474D57; | 6 | $style_color: #474D57; |
5 | $hover: #D9EDF7; | 7 | $hover: #D9EDF7; |
8 | + | ||
9 | +/** | ||
10 | + * Commit Diff Colors | ||
11 | + */ | ||
12 | +$added: #63c363; | ||
13 | +$deleted: #f77; |
app/assets/stylesheets/sections/commits.scss
1 | /** | 1 | /** |
2 | - * | ||
3 | - * COMMIT SHOw | ||
4 | - * | 2 | + * Commit file |
5 | */ | 3 | */ |
6 | .commit-committer-link, | 4 | .commit-committer-link, |
7 | .commit-author-link { | 5 | .commit-author-link { |
@@ -12,11 +10,11 @@ | @@ -12,11 +10,11 @@ | ||
12 | } | 10 | } |
13 | } | 11 | } |
14 | 12 | ||
15 | -.diff_file { | 13 | +.file { |
16 | border: 1px solid #CCC; | 14 | border: 1px solid #CCC; |
17 | margin-bottom: 1em; | 15 | margin-bottom: 1em; |
18 | 16 | ||
19 | - .diff_file_header { | 17 | + .header { |
20 | @extend .clearfix; | 18 | @extend .clearfix; |
21 | padding: 5px 5px 5px 10px; | 19 | padding: 5px 5px 5px 10px; |
22 | color: #555; | 20 | color: #555; |
@@ -28,32 +26,35 @@ | @@ -28,32 +26,35 @@ | ||
28 | background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); | 26 | background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); |
29 | background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); | 27 | background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); |
30 | 28 | ||
29 | + a{ | ||
30 | + color: $style_color; | ||
31 | + } | ||
32 | + | ||
31 | > span { | 33 | > span { |
32 | - font-family: $monospace; | 34 | + font-family: $monospace_font; |
33 | font-size: 14px; | 35 | font-size: 14px; |
34 | line-height: 30px; | 36 | line-height: 30px; |
35 | } | 37 | } |
36 | 38 | ||
37 | - a.view-commit{ | 39 | + a.view-file{ |
38 | font-weight: bold; | 40 | font-weight: bold; |
39 | } | 41 | } |
40 | 42 | ||
41 | .commit-short-id{ | 43 | .commit-short-id{ |
42 | - font-family: $monospace; | 44 | + font-family: $monospace_font; |
43 | font-size: smaller; | 45 | font-size: smaller; |
44 | } | 46 | } |
45 | 47 | ||
46 | .file-mode{ | 48 | .file-mode{ |
47 | - font-family: $monospace; | 49 | + font-family: $monospace_font; |
48 | } | 50 | } |
49 | } | 51 | } |
50 | - .diff_file_content { | 52 | + .content { |
51 | overflow: auto; | 53 | overflow: auto; |
52 | overflow-y: hidden; | 54 | overflow-y: hidden; |
53 | - background: #fff; | 55 | + background: #FFF; |
54 | color: #333; | 56 | color: #333; |
55 | font-size: 12px; | 57 | font-size: 12px; |
56 | - font-family: $monospace; | ||
57 | .old{ | 58 | .old{ |
58 | span.idiff{ | 59 | span.idiff{ |
59 | background-color: #FAA; | 60 | background-color: #FAA; |
@@ -66,114 +67,274 @@ | @@ -66,114 +67,274 @@ | ||
66 | } | 67 | } |
67 | 68 | ||
68 | table { | 69 | table { |
70 | + font-family: $monospace_font; | ||
71 | + border: none; | ||
72 | + margin: 0px; | ||
73 | + padding: 0px; | ||
69 | td { | 74 | td { |
70 | line-height: 18px; | 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 | text-align: center; | 122 | text-align: center; |
77 | - .image { | 123 | + padding: 30px; |
124 | + .wrap{ | ||
78 | display: inline-block; | 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 | img{ | 132 | img{ |
133 | + border: 1px solid #FFF; | ||
83 | background: url('trans_bg.gif'); | 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 | text-decoration: underline; | 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 | /** COMMIT BLOCK **/ | 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 | .commit { | 338 | .commit { |
178 | .browse_code_link_holder { | 339 | .browse_code_link_holder { |
179 | @extend .span2; | 340 | @extend .span2; |
@@ -199,11 +360,10 @@ | @@ -199,11 +360,10 @@ | ||
199 | float: left; | 360 | float: left; |
200 | @extend .lined; | 361 | @extend .lined; |
201 | min-width: 65px; | 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 | .file-stats a { | 367 | .file-stats a { |
208 | color: $style_color; | 368 | color: $style_color; |
209 | } | 369 | } |
@@ -237,7 +397,7 @@ | @@ -237,7 +397,7 @@ | ||
237 | font-size: 13px; | 397 | font-size: 13px; |
238 | background: #474D57; | 398 | background: #474D57; |
239 | color: #fff; | 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,7 +77,7 @@ li.merge_request { | ||
77 | font-size: 14px; | 77 | font-size: 14px; |
78 | background: #474D57; | 78 | background: #474D57; |
79 | color: #fff; | 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 | .mr_source_commit, | 83 | .mr_source_commit, |
app/assets/stylesheets/sections/notes.scss
@@ -40,13 +40,13 @@ ul.notes { | @@ -40,13 +40,13 @@ ul.notes { | ||
40 | .discussion-body { | 40 | .discussion-body { |
41 | margin-left: 50px; | 41 | margin-left: 50px; |
42 | 42 | ||
43 | - .diff_file, | 43 | + .file, |
44 | .discussion-hidden, | 44 | .discussion-hidden, |
45 | .notes { | 45 | .notes { |
46 | @extend .borders; | 46 | @extend .borders; |
47 | background-color: #F9F9F9; | 47 | background-color: #F9F9F9; |
48 | } | 48 | } |
49 | - .diff_file .notes { | 49 | + .file .notes { |
50 | /* reset */ | 50 | /* reset */ |
51 | background: inherit; | 51 | background: inherit; |
52 | border: none; | 52 | border: none; |
@@ -109,7 +109,7 @@ ul.notes { | @@ -109,7 +109,7 @@ ul.notes { | ||
109 | } | 109 | } |
110 | } | 110 | } |
111 | 111 | ||
112 | -.diff_file .notes_holder { | 112 | +.file .notes_holder { |
113 | font-family: $sansFontFamily; | 113 | font-family: $sansFontFamily; |
114 | font-size: 13px; | 114 | font-size: 13px; |
115 | line-height: 18px; | 115 | line-height: 18px; |
@@ -134,8 +134,6 @@ ul.notes { | @@ -134,8 +134,6 @@ ul.notes { | ||
134 | } | 134 | } |
135 | } | 135 | } |
136 | 136 | ||
137 | - | ||
138 | - | ||
139 | /** | 137 | /** |
140 | * Actions for Discussions/Notes | 138 | * Actions for Discussions/Notes |
141 | */ | 139 | */ |
@@ -171,7 +169,7 @@ ul.notes { | @@ -171,7 +169,7 @@ ul.notes { | ||
171 | } | 169 | } |
172 | } | 170 | } |
173 | } | 171 | } |
174 | -.diff_file .note .note-actions { | 172 | +.file .note .note-actions { |
175 | right: 0; | 173 | right: 0; |
176 | top: 0; | 174 | top: 0; |
177 | } | 175 | } |
@@ -182,7 +180,7 @@ ul.notes { | @@ -182,7 +180,7 @@ ul.notes { | ||
182 | * Line note button on the side of diffs | 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 | .add-diff-note { | 184 | .add-diff-note { |
187 | background: url("diff_note_add.png") no-repeat left 0; | 185 | background: url("diff_note_add.png") no-repeat left 0; |
188 | height: 22px; | 186 | height: 22px; |
@@ -212,8 +210,6 @@ ul.notes { | @@ -212,8 +210,6 @@ ul.notes { | ||
212 | } | 210 | } |
213 | } | 211 | } |
214 | 212 | ||
215 | - | ||
216 | - | ||
217 | /** | 213 | /** |
218 | * Note Form | 214 | * Note Form |
219 | */ | 215 | */ |
@@ -222,7 +218,12 @@ ul.notes { | @@ -222,7 +218,12 @@ ul.notes { | ||
222 | .reply-btn { | 218 | .reply-btn { |
223 | @extend .save-btn; | 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 | .discussion { | 227 | .discussion { |
227 | .new_note { | 228 | .new_note { |
228 | margin: 8px 5px 8px 0; | 229 | margin: 8px 5px 8px 0; |
app/assets/stylesheets/sections/projects.scss
@@ -0,0 +1,11 @@ | @@ -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
1 | -class Admin::DashboardController < AdminController | 1 | +class Admin::DashboardController < Admin::ApplicationController |
2 | def index | 2 | def index |
3 | @projects = Project.order("created_at DESC").limit(10) | 3 | @projects = Project.order("created_at DESC").limit(10) |
4 | @users = User.order("created_at DESC").limit(10) | 4 | @users = User.order("created_at DESC").limit(10) |
app/controllers/admin/groups_controller.rb
1 | -class Admin::GroupsController < AdminController | 1 | +class Admin::GroupsController < Admin::ApplicationController |
2 | before_filter :group, only: [:edit, :show, :update, :destroy, :project_update, :project_teams_update] | 2 | before_filter :group, only: [:edit, :show, :update, :destroy, :project_update, :project_teams_update] |
3 | 3 | ||
4 | def index | 4 | def index |
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 @@ | @@ -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 @@ | @@ -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 | before_filter :project, only: [:edit, :show, :update, :destroy, :team_update] | 2 | before_filter :project, only: [:edit, :show, :update, :destroy, :team_update] |
3 | 3 | ||
4 | def index | 4 | def index |
@@ -29,7 +29,9 @@ class Admin::ProjectsController < AdminController | @@ -29,7 +29,9 @@ class Admin::ProjectsController < AdminController | ||
29 | end | 29 | end |
30 | 30 | ||
31 | def update | 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 | if status | 36 | if status |
35 | redirect_to [:admin, @project], notice: 'Project was successfully updated.' | 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,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 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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 | def index | 4 | def index |
3 | @admin_users = User.scoped | 5 | @admin_users = User.scoped |
4 | @admin_users = @admin_users.filter(params[:filter]) | 6 | @admin_users = @admin_users.filter(params[:filter]) |
@@ -7,25 +9,18 @@ class Admin::UsersController < AdminController | @@ -7,25 +9,18 @@ class Admin::UsersController < AdminController | ||
7 | end | 9 | end |
8 | 10 | ||
9 | def show | 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 | end | 14 | end |
18 | 15 | ||
19 | def team_update | 16 | def team_update |
20 | - @admin_user = User.find(params[:id]) | ||
21 | - | ||
22 | UsersProject.add_users_into_projects( | 17 | UsersProject.add_users_into_projects( |
23 | params[:project_ids], | 18 | params[:project_ids], |
24 | - [@admin_user.id], | 19 | + [admin_user.id], |
25 | params[:project_access] | 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 | end | 24 | end |
30 | 25 | ||
31 | 26 | ||
@@ -34,13 +29,11 @@ class Admin::UsersController < AdminController | @@ -34,13 +29,11 @@ class Admin::UsersController < AdminController | ||
34 | end | 29 | end |
35 | 30 | ||
36 | def edit | 31 | def edit |
37 | - @admin_user = User.find(params[:id]) | 32 | + admin_user |
38 | end | 33 | end |
39 | 34 | ||
40 | def block | 35 | def block |
41 | - @admin_user = User.find(params[:id]) | ||
42 | - | ||
43 | - if @admin_user.block | 36 | + if admin_user.block |
44 | redirect_to :back, alert: "Successfully blocked" | 37 | redirect_to :back, alert: "Successfully blocked" |
45 | else | 38 | else |
46 | redirect_to :back, alert: "Error occured. User was not blocked" | 39 | redirect_to :back, alert: "Error occured. User was not blocked" |
@@ -48,9 +41,7 @@ class Admin::UsersController < AdminController | @@ -48,9 +41,7 @@ class Admin::UsersController < AdminController | ||
48 | end | 41 | end |
49 | 42 | ||
50 | def unblock | 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 | redirect_to :back, alert: "Successfully unblocked" | 45 | redirect_to :back, alert: "Successfully unblocked" |
55 | else | 46 | else |
56 | redirect_to :back, alert: "Error occured. User was not unblocked" | 47 | redirect_to :back, alert: "Error occured. User was not unblocked" |
@@ -82,30 +73,34 @@ class Admin::UsersController < AdminController | @@ -82,30 +73,34 @@ class Admin::UsersController < AdminController | ||
82 | params[:user].delete(:password_confirmation) | 73 | params[:user].delete(:password_confirmation) |
83 | end | 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 | respond_to do |format| | 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 | format.json { head :ok } | 81 | format.json { head :ok } |
92 | else | 82 | else |
93 | format.html { render action: "edit" } | 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 | end | 85 | end |
96 | end | 86 | end |
97 | end | 87 | end |
98 | 88 | ||
99 | def destroy | 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 | redirect_to admin_users_path, alert: "User is a project owner and can't be removed." and return | 91 | redirect_to admin_users_path, alert: "User is a project owner and can't be removed." and return |
103 | end | 92 | end |
104 | - @admin_user.destroy | 93 | + admin_user.destroy |
105 | 94 | ||
106 | respond_to do |format| | 95 | respond_to do |format| |
107 | - format.html { redirect_to admin_users_url } | 96 | + format.html { redirect_to admin_users_path } |
108 | format.json { head :ok } | 97 | format.json { head :ok } |
109 | end | 98 | end |
110 | end | 99 | end |
100 | + | ||
101 | + protected | ||
102 | + | ||
103 | + def admin_user | ||
104 | + @admin_user ||= User.find_by_username!(params[:id]) | ||
105 | + end | ||
111 | end | 106 | end |
app/controllers/admin_controller.rb
@@ -1,11 +0,0 @@ | @@ -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,6 +94,18 @@ class ApplicationController < ActionController::Base | ||
94 | return access_denied! unless can?(current_user, :download_code, project) | 94 | return access_denied! unless can?(current_user, :download_code, project) |
95 | end | 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 | def access_denied! | 109 | def access_denied! |
98 | render "errors/access_denied", layout: "errors", status: 404 | 110 | render "errors/access_denied", layout: "errors", status: 404 |
99 | end | 111 | end |
@@ -135,4 +147,5 @@ class ApplicationController < ActionController::Base | @@ -135,4 +147,5 @@ class ApplicationController < ActionController::Base | ||
135 | def dev_tools | 147 | def dev_tools |
136 | Rack::MiniProfiler.authorize_request | 148 | Rack::MiniProfiler.authorize_request |
137 | end | 149 | end |
150 | + | ||
138 | end | 151 | end |
app/controllers/dashboard_controller.rb
1 | class DashboardController < ApplicationController | 1 | class DashboardController < ApplicationController |
2 | respond_to :html | 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 | @groups = current_user.authorized_groups | 8 | @groups = current_user.authorized_groups |
9 | - | ||
10 | @has_authorized_projects = @projects.count > 0 | 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 | @events = Event.in_projects(current_user.authorized_projects.pluck(:id)) | 14 | @events = Event.in_projects(current_user.authorized_projects.pluck(:id)) |
24 | @events = @event_filter.apply_filter(@events) | 15 | @events = @event_filter.apply_filter(@events) |
@@ -33,6 +24,19 @@ class DashboardController < ApplicationController | @@ -33,6 +24,19 @@ class DashboardController < ApplicationController | ||
33 | end | 24 | end |
34 | end | 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 | # Get authored or assigned open merge requests | 40 | # Get authored or assigned open merge requests |
37 | def merge_requests | 41 | def merge_requests |
38 | @merge_requests = current_user.cared_merge_requests | 42 | @merge_requests = current_user.cared_merge_requests |
@@ -55,7 +59,7 @@ class DashboardController < ApplicationController | @@ -55,7 +59,7 @@ class DashboardController < ApplicationController | ||
55 | 59 | ||
56 | protected | 60 | protected |
57 | 61 | ||
58 | - def projects | 62 | + def load_projects |
59 | @projects = current_user.authorized_projects.sorted_by_activity | 63 | @projects = current_user.authorized_projects.sorted_by_activity |
60 | end | 64 | end |
61 | 65 |
app/controllers/groups_controller.rb
1 | class GroupsController < ApplicationController | 1 | class GroupsController < ApplicationController |
2 | respond_to :html | 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 | # Authorize | 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 | def show | 30 | def show |
12 | @events = Event.in_projects(project_ids).limit(20).offset(params[:offset] || 0) | 31 | @events = Event.in_projects(project_ids).limit(20).offset(params[:offset] || 0) |
@@ -85,4 +104,8 @@ class GroupsController < ApplicationController | @@ -85,4 +104,8 @@ class GroupsController < ApplicationController | ||
85 | return render_404 | 104 | return render_404 |
86 | end | 105 | end |
87 | end | 106 | end |
107 | + | ||
108 | + def authorize_create_group! | ||
109 | + can?(current_user, :create_group, nil) | ||
110 | + end | ||
88 | end | 111 | end |
@@ -0,0 +1,27 @@ | @@ -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,7 +19,7 @@ class ProjectsController < ProjectResourceController | ||
19 | end | 19 | end |
20 | 20 | ||
21 | def create | 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 | respond_to do |format| | 24 | respond_to do |format| |
25 | flash[:notice] = 'Project was successfully created.' if @project.saved? | 25 | flash[:notice] = 'Project was successfully created.' if @project.saved? |
@@ -35,7 +35,7 @@ class ProjectsController < ProjectResourceController | @@ -35,7 +35,7 @@ class ProjectsController < ProjectResourceController | ||
35 | end | 35 | end |
36 | 36 | ||
37 | def update | 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 | respond_to do |format| | 40 | respond_to do |format| |
41 | if status | 41 | if status |
app/controllers/search_controller.rb
1 | class SearchController < ApplicationController | 1 | class SearchController < ApplicationController |
2 | def show | 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 | @projects = result[:projects] | 17 | @projects = result[:projects] |
6 | @merge_requests = result[:merge_requests] | 18 | @merge_requests = result[:merge_requests] |
app/controllers/team_members_controller.rb
@@ -4,15 +4,16 @@ class TeamMembersController < ProjectResourceController | @@ -4,15 +4,16 @@ class TeamMembersController < ProjectResourceController | ||
4 | before_filter :authorize_admin_project!, except: [:index, :show] | 4 | before_filter :authorize_admin_project!, except: [:index, :show] |
5 | 5 | ||
6 | def index | 6 | def index |
7 | + @teams = UserTeam.scoped | ||
7 | end | 8 | end |
8 | 9 | ||
9 | def show | 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 | end | 13 | end |
13 | 14 | ||
14 | def new | 15 | def new |
15 | - @team_member = project.users_projects.new | 16 | + @user_project_relation = project.users_projects.new |
16 | end | 17 | end |
17 | 18 | ||
18 | def create | 19 | def create |
@@ -28,18 +29,18 @@ class TeamMembersController < ProjectResourceController | @@ -28,18 +29,18 @@ class TeamMembersController < ProjectResourceController | ||
28 | end | 29 | end |
29 | 30 | ||
30 | def update | 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 | flash[:alert] = "User should have at least one role" | 36 | flash[:alert] = "User should have at least one role" |
36 | end | 37 | end |
37 | redirect_to project_team_index_path(@project) | 38 | redirect_to project_team_index_path(@project) |
38 | end | 39 | end |
39 | 40 | ||
40 | def destroy | 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 | respond_to do |format| | 45 | respond_to do |format| |
45 | format.html { redirect_to project_team_index_path(@project) } | 46 | format.html { redirect_to project_team_index_path(@project) } |
@@ -54,4 +55,10 @@ class TeamMembersController < ProjectResourceController | @@ -54,4 +55,10 @@ class TeamMembersController < ProjectResourceController | ||
54 | 55 | ||
55 | redirect_to project_team_members_path(project), notice: notice | 56 | redirect_to project_team_members_path(project), notice: notice |
56 | end | 57 | end |
58 | + | ||
59 | + protected | ||
60 | + | ||
61 | + def member | ||
62 | + @member ||= User.find_by_username(params[:id]) | ||
63 | + end | ||
57 | end | 64 | end |
@@ -0,0 +1,49 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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
@@ -8,4 +8,8 @@ class UserDecorator < ApplicationDecorator | @@ -8,4 +8,8 @@ class UserDecorator < ApplicationDecorator | ||
8 | def tm_of(project) | 8 | def tm_of(project) |
9 | project.team_member_by_id(self.id) | 9 | project.team_member_by_id(self.id) |
10 | end | 10 | end |
11 | + | ||
12 | + def name_with_email | ||
13 | + "#{name} (#{email})" | ||
14 | + end | ||
11 | end | 15 | end |
app/helpers/application_helper.rb
@@ -72,8 +72,9 @@ module ApplicationHelper | @@ -72,8 +72,9 @@ module ApplicationHelper | ||
72 | end | 72 | end |
73 | 73 | ||
74 | def search_autocomplete_source | 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 | default_nav = [ | 79 | default_nav = [ |
79 | { label: "My Profile", url: profile_path }, | 80 | { label: "My Profile", url: profile_path }, |
@@ -83,29 +84,29 @@ module ApplicationHelper | @@ -83,29 +84,29 @@ module ApplicationHelper | ||
83 | ] | 84 | ] |
84 | 85 | ||
85 | help_nav = [ | 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 | project_nav = [] | 98 | project_nav = [] |
98 | if @project && @project.repository && @project.repository.root_ref | 99 | if @project && @project.repository && @project.repository.root_ref |
99 | project_nav = [ | 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 | end | 111 | end |
111 | 112 |
app/helpers/commits_helper.rb
@@ -59,9 +59,9 @@ module CommitsHelper | @@ -59,9 +59,9 @@ module CommitsHelper | ||
59 | 59 | ||
60 | def image_diff_class(diff) | 60 | def image_diff_class(diff) |
61 | if diff.deleted_file | 61 | if diff.deleted_file |
62 | - "diff_removed" | 62 | + "deleted" |
63 | elsif diff.new_file | 63 | elsif diff.new_file |
64 | - "diff_added" | 64 | + "added" |
65 | else | 65 | else |
66 | nil | 66 | nil |
67 | end | 67 | end |
app/helpers/dashboard_helper.rb
@@ -9,9 +9,9 @@ module DashboardHelper | @@ -9,9 +9,9 @@ module DashboardHelper | ||
9 | 9 | ||
10 | case entity | 10 | case entity |
11 | when 'issue' then | 11 | when 'issue' then |
12 | - dashboard_issues_path(options) | 12 | + issues_dashboard_path(options) |
13 | when 'merge_request' | 13 | when 'merge_request' |
14 | - dashboard_merge_requests_path(options) | 14 | + merge_requests_dashboard_path(options) |
15 | end | 15 | end |
16 | end | 16 | end |
17 | 17 |
app/helpers/projects_helper.rb
@@ -3,8 +3,12 @@ module ProjectsHelper | @@ -3,8 +3,12 @@ module ProjectsHelper | ||
3 | @project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access) | 3 | @project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access) |
4 | end | 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 | end | 12 | end |
9 | 13 | ||
10 | def link_to_project project | 14 | def link_to_project project |
@@ -51,7 +55,9 @@ module ProjectsHelper | @@ -51,7 +55,9 @@ module ProjectsHelper | ||
51 | 55 | ||
52 | def project_title project | 56 | def project_title project |
53 | if project.group | 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 | else | 61 | else |
56 | project.name | 62 | project.name |
57 | end | 63 | end |
app/helpers/tab_helper.rb
@@ -39,7 +39,12 @@ module TabHelper | @@ -39,7 +39,12 @@ module TabHelper | ||
39 | # Returns a list item element String | 39 | # Returns a list item element String |
40 | def nav_link(options = {}, &block) | 40 | def nav_link(options = {}, &block) |
41 | if path = options.delete(:path) | 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 | else | 48 | else |
44 | c = options.delete(:controller) | 49 | c = options.delete(:controller) |
45 | a = options.delete(:action) | 50 | a = options.delete(:action) |
@@ -0,0 +1,26 @@ | @@ -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 | class Ability | 1 | class Ability |
2 | class << self | 2 | class << self |
3 | - def allowed(object, subject) | 3 | + def allowed(user, subject) |
4 | + return [] unless user.kind_of?(User) | ||
5 | + | ||
4 | case subject.class.name | 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 | else [] | 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 | end | 23 | end |
14 | 24 | ||
15 | def project_abilities(user, project) | 25 | def project_abilities(user, project) |
@@ -110,6 +120,22 @@ class Ability | @@ -110,6 +120,22 @@ class Ability | ||
110 | rules.flatten | 120 | rules.flatten |
111 | end | 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 | [:issue, :note, :snippet, :merge_request].each do |name| | 139 | [:issue, :note, :snippet, :merge_request].each do |name| |
114 | define_method "#{name}_abilities" do |user, subject| | 140 | define_method "#{name}_abilities" do |user, subject| |
115 | if subject.author == user | 141 | if subject.author == user |
app/models/concerns/issuable.rb
@@ -22,6 +22,7 @@ module Issuable | @@ -22,6 +22,7 @@ module Issuable | ||
22 | scope :opened, where(closed: false) | 22 | scope :opened, where(closed: false) |
23 | scope :closed, where(closed: true) | 23 | scope :closed, where(closed: true) |
24 | scope :of_group, ->(group) { where(project_id: group.project_ids) } | 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 | scope :assigned, ->(u) { where(assignee_id: u.id)} | 26 | scope :assigned, ->(u) { where(assignee_id: u.id)} |
26 | scope :recent, order("created_at DESC") | 27 | scope :recent, order("created_at DESC") |
27 | 28 |
app/models/project.rb
@@ -33,28 +33,31 @@ class Project < ActiveRecord::Base | @@ -33,28 +33,31 @@ class Project < ActiveRecord::Base | ||
33 | attr_accessor :error_code | 33 | attr_accessor :error_code |
34 | 34 | ||
35 | # Relations | 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 | belongs_to :namespace | 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 | has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', foreign_key: 'project_id' | 40 | has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', foreign_key: 'project_id' |
56 | has_one :gitlab_ci_service, dependent: :destroy | 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 | delegate :name, to: :owner, allow_nil: true, prefix: true | 61 | delegate :name, to: :owner, allow_nil: true, prefix: true |
59 | 62 | ||
60 | # Validations | 63 | # Validations |
@@ -77,6 +80,8 @@ class Project < ActiveRecord::Base | @@ -77,6 +80,8 @@ class Project < ActiveRecord::Base | ||
77 | # Scopes | 80 | # Scopes |
78 | scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) } | 81 | scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) } |
79 | scope :not_in_group, ->(group) { where("id NOT IN (:ids)", ids: group.project_ids ) } | 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 | scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) } | 85 | scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) } |
81 | scope :sorted_by_activity, ->() { order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") } | 86 | scope :sorted_by_activity, ->() { order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") } |
82 | scope :personal, ->(user) { where(namespace_id: user.namespace_id) } | 87 | scope :personal, ->(user) { where(namespace_id: user.namespace_id) } |
@@ -122,7 +127,7 @@ class Project < ActiveRecord::Base | @@ -122,7 +127,7 @@ class Project < ActiveRecord::Base | ||
122 | end | 127 | end |
123 | 128 | ||
124 | def team | 129 | def team |
125 | - @team ||= Team.new(self) | 130 | + @team ||= ProjectTeam.new(self) |
126 | end | 131 | end |
127 | 132 | ||
128 | def repository | 133 | def repository |
@@ -335,7 +340,7 @@ class Project < ActiveRecord::Base | @@ -335,7 +340,7 @@ class Project < ActiveRecord::Base | ||
335 | end | 340 | end |
336 | 341 | ||
337 | def execute_hooks(data) | 342 | def execute_hooks(data) |
338 | - hooks.each { |hook| hook.execute(data) } | 343 | + hooks.each { |hook| hook.async_execute(data) } |
339 | end | 344 | end |
340 | 345 | ||
341 | def execute_services(data) | 346 | def execute_services(data) |
@@ -489,6 +494,11 @@ class Project < ActiveRecord::Base | @@ -489,6 +494,11 @@ class Project < ActiveRecord::Base | ||
489 | http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') | 494 | http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') |
490 | end | 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 | # Check if current branch name is marked as protected in the system | 502 | # Check if current branch name is marked as protected in the system |
493 | def protected_branch? branch_name | 503 | def protected_branch? branch_name |
494 | protected_branches.map(&:name).include?(branch_name) | 504 | protected_branches.map(&:name).include?(branch_name) |
@@ -0,0 +1,122 @@ | @@ -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,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,23 +40,32 @@ class User < ActiveRecord::Base | ||
40 | attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username, | 40 | attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username, |
41 | :skype, :linkedin, :twitter, :dark_scheme, :theme_id, :force_random_password, | 41 | :skype, :linkedin, :twitter, :dark_scheme, :theme_id, :force_random_password, |
42 | :extern_uid, :provider, as: [:default, :admin] | 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 | attr_accessor :force_random_password | 45 | attr_accessor :force_random_password |
46 | 46 | ||
47 | # Namespace for personal projects | 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 | validates :name, presence: true | 70 | validates :name, presence: true |
62 | validates :bio, length: { within: 0..255 } | 71 | validates :bio, length: { within: 0..255 } |
@@ -80,6 +89,9 @@ class User < ActiveRecord::Base | @@ -80,6 +89,9 @@ class User < ActiveRecord::Base | ||
80 | scope :blocked, where(blocked: true) | 89 | scope :blocked, where(blocked: true) |
81 | scope :active, where(blocked: false) | 90 | scope :active, where(blocked: false) |
82 | scope :alphabetically, order('name ASC') | 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 | # Class methods | 97 | # Class methods |
@@ -131,6 +143,11 @@ class User < ActiveRecord::Base | @@ -131,6 +143,11 @@ class User < ActiveRecord::Base | ||
131 | # | 143 | # |
132 | # Instance methods | 144 | # Instance methods |
133 | # | 145 | # |
146 | + | ||
147 | + def to_param | ||
148 | + username | ||
149 | + end | ||
150 | + | ||
134 | def generate_password | 151 | def generate_password |
135 | if self.force_random_password | 152 | if self.force_random_password |
136 | self.password = self.password_confirmation = Devise.friendly_token.first(8) | 153 | self.password = self.password_confirmation = Devise.friendly_token.first(8) |
@@ -220,7 +237,7 @@ class User < ActiveRecord::Base | @@ -220,7 +237,7 @@ class User < ActiveRecord::Base | ||
220 | end | 237 | end |
221 | 238 | ||
222 | def can_create_group? | 239 | def can_create_group? |
223 | - is_admin? | 240 | + can?(:create_group, nil) |
224 | end | 241 | end |
225 | 242 | ||
226 | def abilities | 243 | def abilities |
@@ -283,4 +300,15 @@ class User < ActiveRecord::Base | @@ -283,4 +300,15 @@ class User < ActiveRecord::Base | ||
283 | def namespace_id | 300 | def namespace_id |
284 | namespace.try :id | 301 | namespace.try :id |
285 | end | 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 | end | 314 | end |
@@ -0,0 +1,97 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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,7 +39,10 @@ class UsersProject < ActiveRecord::Base | ||
39 | scope :reporters, where(project_access: REPORTER) | 39 | scope :reporters, where(project_access: REPORTER) |
40 | scope :developers, where(project_access: DEVELOPER) | 40 | scope :developers, where(project_access: DEVELOPER) |
41 | scope :masters, where(project_access: MASTER) | 41 | scope :masters, where(project_access: MASTER) |
42 | + | ||
42 | scope :in_project, ->(project) { where(project_id: project.id) } | 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 | class << self | 47 | class << self |
45 | 48 |
app/models/web_hook.rb
@@ -34,4 +34,8 @@ class WebHook < ActiveRecord::Base | @@ -34,4 +34,8 @@ class WebHook < ActiveRecord::Base | ||
34 | basic_auth: {username: parsed_url.user, password: parsed_url.password}) | 34 | basic_auth: {username: parsed_url.user, password: parsed_url.password}) |
35 | end | 35 | end |
36 | end | 36 | end |
37 | + | ||
38 | + def async_execute(data) | ||
39 | + Sidekiq::Client.enqueue(ProjectWebHookWorker, id, data) | ||
40 | + end | ||
37 | end | 41 | end |
app/views/admin/groups/show.html.haml
@@ -72,16 +72,17 @@ | @@ -72,16 +72,17 @@ | ||
72 | %th Users | 72 | %th Users |
73 | %th Project Access: | 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 | %td.projects_access | 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 | - next unless u_p | 82 | - next unless u_p |
82 | %span | 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 | %tr | 86 | %tr |
86 | %td.input= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5' | 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 | %td= select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3"} | 88 | %td= select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3"} |
@@ -0,0 +1,16 @@ | @@ -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,9 +114,9 @@ | ||
114 | %h5 | 114 | %h5 |
115 | Team | 115 | Team |
116 | %small | 116 | %small |
117 | - (#{@project.users_projects.count}) | 117 | + (#{@project.users.count}) |
118 | %br | 118 | %br |
119 | -%table.zebra-striped | 119 | +%table.zebra-striped.team_members |
120 | %thead | 120 | %thead |
121 | %tr | 121 | %tr |
122 | %th Name | 122 | %th Name |
@@ -124,13 +124,13 @@ | @@ -124,13 +124,13 @@ | ||
124 | %th Repository Access | 124 | %th Repository Access |
125 | %th | 125 | %th |
126 | 126 | ||
127 | - - @project.users_projects.each do |tm| | 127 | + - @project.users.each do |tm| |
128 | %tr | 128 | %tr |
129 | %td | 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 | %br | 135 | %br |
136 | %h5 Add new team member | 136 | %h5 Add new team member |
app/views/admin/team_members/_form.html.haml
@@ -1,16 +0,0 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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 @@ | @@ -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,6 +47,14 @@ | ||
47 | .input= f.number_field :projects_limit | 47 | .input= f.number_field :projects_limit |
48 | 48 | ||
49 | .clearfix | 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 | = f.label :admin do | 58 | = f.label :admin do |
51 | %strong.cred Administrator | 59 | %strong.cred Administrator |
52 | .input= f.check_box :admin | 60 | .input= f.check_box :admin |
app/views/admin/users/show.html.haml
@@ -123,5 +123,5 @@ | @@ -123,5 +123,5 @@ | ||
123 | %tr | 123 | %tr |
124 | %td= link_to project.name_with_namespace, admin_project_path(project) | 124 | %td= link_to project.name_with_namespace, admin_project_path(project) |
125 | %td= tm.project_access_human | 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,19 +11,7 @@ | ||
11 | 11 | ||
12 | :javascript | 12 | :javascript |
13 | $(function(){ | 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,50 +12,38 @@ | ||
12 | .file-stats | 12 | .file-stats |
13 | = render "commits/diff_head", diffs: diffs | 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 | View file @ | 37 | View file @ |
29 | %span.commit-short-id= @commit.short_id(6) | 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 | - else | 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 @@ | @@ -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 | \ No newline at end of file | 64 | \ No newline at end of file |
app/views/commits/_text_diff.html.haml
@@ -1,23 +0,0 @@ | @@ -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 @@ | @@ -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 | %h5.title | 2 | %h5.title |
3 | Groups | 3 | Groups |
4 | %small | 4 | %small |
5 | (#{groups.count}) | 5 | (#{groups.count}) |
6 | - if current_user.can_create_group? | 6 | - if current_user.can_create_group? |
7 | %span.right | 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 | %i.icon-plus | 9 | %i.icon-plus |
10 | New Group | 10 | New Group |
11 | %ul.well-list | 11 | %ul.well-list |
@@ -13,8 +13,6 @@ | @@ -13,8 +13,6 @@ | ||
13 | %li | 13 | %li |
14 | = link_to group_path(id: group.path), class: dom_class(group) do | 14 | = link_to group_path(id: group.path), class: dom_class(group) do |
15 | %strong.well-title= truncate(group.name, length: 35) | 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,19 +2,12 @@ | ||
2 | %h5.title | 2 | %h5.title |
3 | Projects | 3 | Projects |
4 | %small | 4 | %small |
5 | - (#{projects.total_count}) | 5 | + (#{@projects_count}) |
6 | - if current_user.can_create_project? | 6 | - if current_user.can_create_project? |
7 | %span.right | 7 | %span.right |
8 | = link_to new_project_path, class: "btn very_small info" do | 8 | = link_to new_project_path, class: "btn very_small info" do |
9 | %i.icon-plus | 9 | %i.icon-plus |
10 | New Project | 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 | %ul.well-list | 12 | %ul.well-list |
20 | - projects.each do |project| | 13 | - projects.each do |project| |
@@ -33,4 +26,6 @@ | @@ -33,4 +26,6 @@ | ||
33 | - if projects.blank? | 26 | - if projects.blank? |
34 | %li | 27 | %li |
35 | %h3.nothing_here_message There are no projects here. | 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 @@ | @@ -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,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