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