Commit 66ebf8d8 authored by Lennart Rosam's avatar Lennart Rosam

Merge remote-tracking branch 'github/master'

parents dc13af90 bd948549
web: bundle exec unicorn_rails -p $PORT web: bundle exec unicorn_rails -p $PORT
worker: bundle exec sidekiq -q post_receive,mailer,system_hook,common,default worker: bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,common,default
class CommitFile
constructor: (file) ->
if $('.image', file).length
new ImageFile(file)
this.CommitFile = CommitFile
\ No newline at end of file
class ImageFile
# Width where images must fits in, for 2-up this gets divided by 2
@availWidth = 900
@viewModes = ['two-up', 'swipe']
constructor: (@file) ->
# Determine if old and new file has same dimensions, if not show 'two-up' view
this.requestImageInfo $('.two-up.view .frame.deleted img', @file), (deletedWidth, deletedHeight) =>
this.requestImageInfo $('.two-up.view .frame.added img', @file), (width, height) =>
if width == deletedWidth && height == deletedHeight
this.initViewModes()
else
this.initView('two-up')
initViewModes: ->
viewMode = ImageFile.viewModes[0]
$('.view-modes', @file).removeClass 'hide'
$('.view-modes-menu', @file).on 'click', 'li', (event) =>
unless $(event.currentTarget).hasClass('active')
this.activateViewMode(event.currentTarget.className)
this.activateViewMode(viewMode)
activateViewMode: (viewMode) ->
$('.view-modes-menu li', @file)
.removeClass('active')
.filter(".#{viewMode}").addClass 'active'
$(".view:visible:not(.#{viewMode})", @file).fadeOut 200, =>
$(".view.#{viewMode}", @file).fadeIn(200)
this.initView viewMode
initView: (viewMode) ->
this.views[viewMode].call(this)
prepareFrames = (view) ->
maxWidth = 0
maxHeight = 0
$('.frame', view).each (index, frame) =>
width = $(frame).width()
height = $(frame).height()
maxWidth = if width > maxWidth then width else maxWidth
maxHeight = if height > maxHeight then height else maxHeight
.css
width: maxWidth
height: maxHeight
[maxWidth, maxHeight]
views:
'two-up': ->
$('.two-up.view .wrap', @file).each (index, wrap) =>
$('img', wrap).each ->
currentWidth = $(this).width()
if currentWidth > ImageFile.availWidth / 2
$(this).width ImageFile.availWidth / 2
this.requestImageInfo $('img', wrap), (width, height) ->
$('.image-info .meta-width', wrap).text "#{width}px"
$('.image-info .meta-height', wrap).text "#{height}px"
$('.image-info', wrap).removeClass('hide')
'swipe': ->
maxWidth = 0
maxHeight = 0
$('.swipe.view', @file).each (index, view) =>
[maxWidth, maxHeight] = prepareFrames(view)
$('.swipe-frame', view).css
width: maxWidth + 16
height: maxHeight + 28
$('.swipe-wrap', view).css
width: maxWidth + 1
height: maxHeight + 2
$('.swipe-bar', view).css
left: 0
.draggable
axis: 'x'
containment: 'parent'
drag: (event) ->
$('.swipe-wrap', view).width (maxWidth + 1) - $(this).position().left
stop: (event) ->
$('.swipe-wrap', view).width (maxWidth + 1) - $(this).position().left
'onion-skin': ->
maxWidth = 0
maxHeight = 0
dragTrackWidth = $('.drag-track', @file).width() - $('.dragger', @file).width()
$('.onion-skin.view', @file).each (index, view) =>
[maxWidth, maxHeight] = prepareFrames(view)
$('.onion-skin-frame', view).css
width: maxWidth + 16
height: maxHeight + 28
$('.swipe-wrap', view).css
width: maxWidth + 1
height: maxHeight + 2
$('.dragger', view).css
left: dragTrackWidth
.draggable
axis: 'x'
containment: 'parent'
drag: (event) ->
$('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth)
stop: (event) ->
$('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth)
requestImageInfo: (img, callback) ->
domImg = img.get(0)
if domImg.complete
callback.call(this, domImg.naturalWidth, domImg.naturalHeight)
else
img.on 'load', =>
callback.call(this, domImg.naturalWidth, domImg.naturalHeight)
this.ImageFile = ImageFile
\ No newline at end of file
var CommitsList = {
ref:null,
limit:0,
offset:0,
disable:false,
init:
function(ref, limit) {
$(".day-commits-table li.commit").live('click', function(e){
if(e.target.nodeName != "A") {
location.href = $(this).attr("url");
e.stopPropagation();
return false;
}
});
this.ref=ref;
this.limit=limit;
this.offset=limit;
this.initLoadMore();
$('.loading').show();
},
getOld:
function() {
$('.loading').show();
$.ajax({
type: "GET",
url: location.href,
data: "limit=" + this.limit + "&offset=" + this.offset + "&ref=" + this.ref,
complete: function(){ $('.loading').hide()},
dataType: "script"});
},
append:
function(count, html) {
$("#commits_list").append(html);
if(count > 0) {
this.offset += count;
} else {
this.disable = true;
}
},
initLoadMore:
function() {
$(document).endlessScroll({
bottomPixels: 400,
fireDelay: 1000,
fireOnce:true,
ceaseFire: function() {
return CommitsList.disable;
},
callback: function(i) {
CommitsList.getOld();
}
});
}
}
class CommitsList
@data =
ref: null
limit: 0
offset: 0
@disable = false
@showProgress: ->
$('.loading').show()
@hideProgress: ->
$('.loading').hide()
@init: (ref, limit) ->
$(".day-commits-table li.commit").live 'click', (event) ->
if event.target.nodeName != "A"
location.href = $(this).attr("url")
e.stopPropagation()
return false
@data.ref = ref
@data.limit = limit
@data.offset = limit
this.initLoadMore()
this.showProgress();
@getOld: ->
this.showProgress()
$.ajax
type: "GET"
url: location.href
data: @data
complete: this.hideProgress
dataType: "script"
@append: (count, html) ->
$("#commits-list").append(html)
if count > 0
@data.offset += count
else
@disable = true
@initLoadMore: ->
$(document).endlessScroll
bottomPixels: 400
fireDelay: 1000
fireOnce: true
ceaseFire: =>
@disable
callback: =>
this.getOld()
this.CommitsList = CommitsList
\ No newline at end of file
var Pager = {
limit:0,
offset:0,
disable:false,
init:
function(limit, preload) {
this.limit=limit;
if(preload) {
this.offset = 0;
this.getOld();
} else {
this.offset = limit;
}
this.initLoadMore();
},
getOld:
function() {
$('.loading').show();
$.ajax({
type: "GET",
url: location.href,
data: "limit=" + this.limit + "&offset=" + this.offset,
complete: function(){ $('.loading').hide()},
dataType: "script"});
},
append:
function(count, html) {
$(".content_list").append(html);
if(count > 0) {
this.offset += count;
} else {
this.disable = true;
}
},
initLoadMore:
function() {
$(document).endlessScroll({
bottomPixels: 400,
fireDelay: 1000,
fireOnce:true,
ceaseFire: function() {
return Pager.disable;
},
callback: function(i) {
$('.loading').show();
Pager.getOld();
}
});
}
}
@Pager =
limit: 0
offset: 0
disable: false
init: (limit, preload) ->
@limit = limit
if preload
@offset = 0
@getOld()
else
@offset = limit
@initLoadMore()
getOld: ->
$(".loading").show()
$.ajax
type: "GET"
url: location.href
data: "limit=" + @limit + "&offset=" + @offset
complete: ->
$(".loading").hide()
dataType: "script"
append: (count, html) ->
$(".content_list").append html
if count > 0
@offset += count
else
@disable = true
initLoadMore: ->
$(document).endlessScroll
bottomPixels: 400
fireDelay: 1000
fireOnce: true
ceaseFire: ->
Pager.disable
callback: (i) ->
$(".loading").show()
Pager.getOld()
...@@ -277,8 +277,20 @@ p.time { ...@@ -277,8 +277,20 @@ p.time {
} }
} }
.search-holder {
label, input {
height: 30px;
padding: 0;
font-size: 14px;
}
label {
line-height: 30px;
color: #666;
}
}
.highlight_word { .highlight_word {
background: #EEDC94; border-bottom: 2px solid #F90;
} }
.status_info { .status_info {
...@@ -326,7 +338,7 @@ li.note { ...@@ -326,7 +338,7 @@ li.note {
li { li {
border-bottom:none !important; border-bottom:none !important;
} }
.file { .attachment {
padding-left: 20px; padding-left: 20px;
background:url("icon-attachment.png") no-repeat left center; background:url("icon-attachment.png") no-repeat left center;
} }
......
...@@ -135,7 +135,7 @@ ...@@ -135,7 +135,7 @@
pre { pre {
border: none; border: none;
border-radius: 0; border-radius: 0;
font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; font-family: $monospace_font;
font-size: 12px !important; font-size: 12px !important;
line-height: 16px !important; line-height: 16px !important;
margin: 0; margin: 0;
......
...@@ -4,4 +4,4 @@ ...@@ -4,4 +4,4 @@
} }
/** Typo **/ /** Typo **/
$monospace: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace; $monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;
...@@ -21,7 +21,7 @@ h6 { ...@@ -21,7 +21,7 @@ h6 {
/** CODE **/ /** CODE **/
pre { pre {
font-family:'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; font-family: $monospace_font;
&.dark { &.dark {
background: #333; background: #333;
...@@ -79,7 +79,7 @@ a:focus { ...@@ -79,7 +79,7 @@ a:focus {
} }
.monospace { .monospace {
font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; font-family: $monospace_font;
} }
/** /**
......
/** Colors **/ /**
* General Colors
*/
$primary_color: #2FA0BB; $primary_color: #2FA0BB;
$link_color: #3A89A3; $link_color: #3A89A3;
$style_color: #474D57; $style_color: #474D57;
$hover: #D9EDF7; $hover: #D9EDF7;
/**
* Commit Diff Colors
*/
$added: #63c363;
$deleted: #f77;
/** /**
* * Commit file
* COMMIT SHOw
*
*/ */
.commit-committer-link, .commit-committer-link,
.commit-author-link { .commit-author-link {
...@@ -12,11 +10,11 @@ ...@@ -12,11 +10,11 @@
} }
} }
.diff_file { .file {
border: 1px solid #CCC; border: 1px solid #CCC;
margin-bottom: 1em; margin-bottom: 1em;
.diff_file_header { .header {
@extend .clearfix; @extend .clearfix;
padding: 5px 5px 5px 10px; padding: 5px 5px 5px 10px;
color: #555; color: #555;
...@@ -28,32 +26,35 @@ ...@@ -28,32 +26,35 @@
background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf); background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf); background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
a{
color: $style_color;
}
> span { > span {
font-family: $monospace; font-family: $monospace_font;
font-size: 14px; font-size: 14px;
line-height: 30px; line-height: 30px;
} }
a.view-commit{ a.view-file{
font-weight: bold; font-weight: bold;
} }
.commit-short-id{ .commit-short-id{
font-family: $monospace; font-family: $monospace_font;
font-size: smaller; font-size: smaller;
} }
.file-mode{ .file-mode{
font-family: $monospace; font-family: $monospace_font;
} }
} }
.diff_file_content { .content {
overflow: auto; overflow: auto;
overflow-y: hidden; overflow-y: hidden;
background: #fff; background: #FFF;
color: #333; color: #333;
font-size: 12px; font-size: 12px;
font-family: $monospace;
.old{ .old{
span.idiff{ span.idiff{
background-color: #FAA; background-color: #FAA;
...@@ -66,77 +67,28 @@ ...@@ -66,77 +67,28 @@
} }
table { table {
td { font-family: $monospace_font;
line-height: 18px;
}
}
}
.diff_file_content_image {
background: #eee;
text-align: center;
.image {
display: inline-block;
margin: 50px;
max-width: 400px;
img{
background: url('trans_bg.gif');
}
&.diff_removed {
img{
border: 1px solid #C00;
}
}
&.diff_added {
img{
border: 1px solid #0C0;
}
}
.image-info{
margin: 5px 0 0 0;
}
}
&.img_compared {
.image {
max-width: 300px;
}
}
}
}
.diff_file_content{
table {
border: none; border: none;
margin: 0px; margin: 0px;
padding: 0px; padding: 0px;
tr {
td { td {
line-height: 18px;
font-size: 12px; font-size: 12px;
} }
} }
} .old_line, .new_line {
.new_line, margin: 0px;
.old_line, padding: 0px;
.notes_line { border: none;
margin:0px; background: #EEE;
padding:0px; color: #666;
border:none;
background:#EEE;
color:#666;
padding: 0px 5px; padding: 0px 5px;
border-right: 1px solid #ccc; border-right: 1px solid #ccc;
text-align: right; text-align: right;
min-width: 35px; min-width: 35px;
max-width: 35px; max-width: 35px;
width: 35px; width: 35px;
moz-user-select: none; @include user-select(none);
-khtml-user-select: none;
user-select: none;
a { a {
float: left; float: left;
width: 35px; width: 35px;
...@@ -164,16 +116,225 @@ ...@@ -164,16 +116,225 @@
background: #fafafa; background: #fafafa;
} }
} }
}
.image {
background: #ddd;
text-align: center;
padding: 30px;
.wrap{
display: inline-block;
}
.frame {
display: inline-block;
background-color: #fff;
line-height: 0;
img{
border: 1px solid #FFF;
background: url('trans_bg.gif');
}
&.deleted {
border: 1px solid $deleted;
}
&.added {
border: 1px solid $added;
}
}
.image-info{
font-size: 12px;
margin: 5px 0 0 0;
color: grey;
}
.view.swipe{
position: relative;
.swipe-frame{
display: block;
margin: auto;
position: relative;
}
.swipe-wrap{
overflow: hidden;
border-left: 1px solid #999;
position: absolute;
display: block;
top: 13px;
right: 7px;
}
.frame{
top: 0;
right: 0;
position: absolute;
&.deleted{
margin: 0;
display: block;
top: 13px;
right: 7px;
}
}
.swipe-bar{
display: block;
height: 100%;
width: 15px;
z-index: 100;
position: absolute;
cursor: pointer;
&:hover{
.top-handle{
background-position: -15px 3px;
}
.bottom-handle{
background-position: -15px -11px;
}
};
.top-handle{
display: block;
height: 14px;
width: 15px;
position: absolute;
top: 0px;
background: url('swipemode_sprites.gif') 0 3px no-repeat;
}
.bottom-handle{
display: block;
height: 14px;
width: 15px;
position: absolute;
bottom: 0px;
background: url('swipemode_sprites.gif') 0 -11px no-repeat;
}
}
} //.view.swipe
.view.onion-skin{
.onion-skin-frame{
display: block;
margin: auto;
position: relative;
}
.frame.added, .frame.deleted {
position: absolute;
display: block;
top: 0px;
left: 0px;
}
.controls{
display: block;
height: 14px;
width: 300px;
z-index: 100;
position: absolute;
bottom: 0px;
left: 50%;
margin-left: -150px;
.drag-track{
display: block;
position: absolute;
left: 12px;
height: 10px;
width: 276px;
background: url('onion_skin_sprites.gif') -4px -20px repeat-x;
}
.dragger {
display: block;
position: absolute;
left: 0px;
top: 0px;
height: 14px;
width: 14px;
background: url('onion_skin_sprites.gif') 0px -34px repeat-x;
cursor: pointer;
}
.transparent {
display: block;
position: absolute;
top: 2px;
right: 0px;
height: 10px;
width: 10px;
background: url('onion_skin_sprites.gif') -2px 0px no-repeat;
}
.opaque {
display: block;
position: absolute;
top: 2px;
left: 0px;
height: 10px;
width: 10px;
background: url('onion_skin_sprites.gif') -2px -10px no-repeat;
}
}
} //.view.onion-skin
}
.view-modes{
padding: 10px;
text-align: center;
background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));
background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
ul, li{
list-style: none;
margin: 0;
padding: 0;
display: inline-block;
}
li{
color: grey;
border-left: 1px solid #c1c1c1;
padding: 0 12px 0 16px;
cursor: pointer;
&:first-child{
border-left: none;
}
&:hover{
text-decoration: underline;
}
&.active{
&:hover{
text-decoration: none;
}
cursor: default;
color: #333;
}
&.disabled{
display: none;
}
}
}
} }
/** COMMIT BLOCK **/ /** COMMIT BLOCK **/
.commit-title{display: block;} .commit-title{
.commit-title{margin-bottom: 10px} display: block;
.commit-author, .commit-committer{display: block;color: #999; font-weight: normal; font-style: italic;} }
.commit-author strong, .commit-committer strong{font-weight: bold; font-style: normal;} .commit-title{
margin-bottom: 10px;
}
.commit-author, .commit-committer{
display: block;
color: #999;
font-weight: normal;
font-style: italic;
}
.commit-author strong, .commit-committer strong{
font-weight: bold;
font-style: normal;
}
/** COMMIT ROW **/ /**
* COMMIT ROW
*/
.commit { .commit {
.browse_code_link_holder { .browse_code_link_holder {
@extend .span2; @extend .span2;
...@@ -199,11 +360,10 @@ ...@@ -199,11 +360,10 @@
float: left; float: left;
@extend .lined; @extend .lined;
min-width: 65px; min-width: 65px;
font-family: $monospace; font-family: $monospace_font;
} }
} }
.diff_file_header a,
.file-stats a { .file-stats a {
color: $style_color; color: $style_color;
} }
...@@ -237,7 +397,7 @@ ...@@ -237,7 +397,7 @@
font-size: 13px; font-size: 13px;
background: #474D57; background: #474D57;
color: #fff; color: #fff;
font-family: $monospace; font-family: $monospace_font;
} }
......
...@@ -77,7 +77,7 @@ li.merge_request { ...@@ -77,7 +77,7 @@ li.merge_request {
font-size: 14px; font-size: 14px;
background: #474D57; background: #474D57;
color: #fff; color: #fff;
font-family: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono','lucida console',monospace; font-family: $monospace_font;
} }
.mr_source_commit, .mr_source_commit,
......
...@@ -40,13 +40,13 @@ ul.notes { ...@@ -40,13 +40,13 @@ ul.notes {
.discussion-body { .discussion-body {
margin-left: 50px; margin-left: 50px;
.diff_file, .file,
.discussion-hidden, .discussion-hidden,
.notes { .notes {
@extend .borders; @extend .borders;
background-color: #F9F9F9; background-color: #F9F9F9;
} }
.diff_file .notes { .file .notes {
/* reset */ /* reset */
background: inherit; background: inherit;
border: none; border: none;
...@@ -109,7 +109,7 @@ ul.notes { ...@@ -109,7 +109,7 @@ ul.notes {
} }
} }
.diff_file .notes_holder { .file .notes_holder {
font-family: $sansFontFamily; font-family: $sansFontFamily;
font-size: 13px; font-size: 13px;
line-height: 18px; line-height: 18px;
...@@ -134,8 +134,6 @@ ul.notes { ...@@ -134,8 +134,6 @@ ul.notes {
} }
} }
/** /**
* Actions for Discussions/Notes * Actions for Discussions/Notes
*/ */
...@@ -171,7 +169,7 @@ ul.notes { ...@@ -171,7 +169,7 @@ ul.notes {
} }
} }
} }
.diff_file .note .note-actions { .file .note .note-actions {
right: 0; right: 0;
top: 0; top: 0;
} }
...@@ -182,7 +180,7 @@ ul.notes { ...@@ -182,7 +180,7 @@ ul.notes {
* Line note button on the side of diffs * Line note button on the side of diffs
*/ */
.diff_file tr.line_holder { .file tr.line_holder {
.add-diff-note { .add-diff-note {
background: url("diff_note_add.png") no-repeat left 0; background: url("diff_note_add.png") no-repeat left 0;
height: 22px; height: 22px;
...@@ -212,8 +210,6 @@ ul.notes { ...@@ -212,8 +210,6 @@ ul.notes {
} }
} }
/** /**
* Note Form * Note Form
*/ */
...@@ -222,7 +218,12 @@ ul.notes { ...@@ -222,7 +218,12 @@ ul.notes {
.reply-btn { .reply-btn {
@extend .save-btn; @extend .save-btn;
} }
.diff_file, .file .content tr.line_holder:hover > td { background: $hover !important; }
.file .content tr.line_holder:hover > td .line_note_link {
opacity: 1.0;
filter: alpha(opacity=100);
}
.file,
.discussion { .discussion {
.new_note { .new_note {
margin: 8px 5px 8px 0; margin: 8px 5px 8px 0;
......
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
.side { .side {
@extend .right; @extend .right;
.groups_box,
.projects_box { .projects_box {
> .title { > .title {
padding: 2px 15px; padding: 2px 15px;
......
# Provides a base class for Admin controllers to subclass # Provides a base class for Admin controllers to subclass
# #
# Automatically sets the layout and ensures an administrator is logged in # Automatically sets the layout and ensures an administrator is logged in
class AdminController < ApplicationController class Admin::ApplicationController < ApplicationController
layout 'admin' layout 'admin'
before_filter :authenticate_admin! before_filter :authenticate_admin!
......
class Admin::DashboardController < AdminController class Admin::DashboardController < Admin::ApplicationController
def index def index
@projects = Project.order("created_at DESC").limit(10) @projects = Project.order("created_at DESC").limit(10)
@users = User.order("created_at DESC").limit(10) @users = User.order("created_at DESC").limit(10)
......
class Admin::GroupsController < AdminController class Admin::GroupsController < Admin::ApplicationController
before_filter :group, only: [:edit, :show, :update, :destroy, :project_update, :project_teams_update] before_filter :group, only: [:edit, :show, :update, :destroy, :project_update, :project_teams_update]
def index def index
......
class Admin::HooksController < AdminController class Admin::HooksController < Admin::ApplicationController
def index def index
@hooks = SystemHook.all @hooks = SystemHook.all
@hook = SystemHook.new @hook = SystemHook.new
......
class Admin::LogsController < AdminController class Admin::LogsController < Admin::ApplicationController
end end
# Provides a base class for Admin controllers to subclass
#
# Automatically sets the layout and ensures an administrator is logged in
class Admin::Projects::ApplicationController < Admin::ApplicationController
protected
def project
@project ||= Project.find_with_namespace(params[:project_id])
end
end
class Admin::Projects::MembersController < Admin::Projects::ApplicationController
def edit
@member = team_member
@project = project
@team_member_relation = team_member_relation
end
def update
if team_member_relation.update_attributes(params[:team_member])
redirect_to [:admin, project], notice: 'Project Access was successfully updated.'
else
render action: "edit"
end
end
def destroy
team_member_relation.destroy
redirect_to :back
end
private
def team_member
@member ||= project.users.find_by_username(params[:id])
end
def team_member_relation
team_member.users_projects.find_by_project_id(project)
end
end
class Admin::ProjectsController < AdminController class Admin::ProjectsController < Admin::ApplicationController
before_filter :project, only: [:edit, :show, :update, :destroy, :team_update] before_filter :project, only: [:edit, :show, :update, :destroy, :team_update]
def index def index
...@@ -29,7 +29,9 @@ class Admin::ProjectsController < AdminController ...@@ -29,7 +29,9 @@ class Admin::ProjectsController < AdminController
end end
def update def update
status = Projects::UpdateContext.new(project, current_user, params).execute(:admin) project.creator = current_user unless project.creator
status = ::Projects::UpdateContext.new(project, current_user, params).execute(:admin)
if status if status
redirect_to [:admin, @project], notice: 'Project was successfully updated.' redirect_to [:admin, @project], notice: 'Project was successfully updated.'
......
class Admin::ResqueController < AdminController class Admin::ResqueController < Admin::ApplicationController
def show def show
end end
end end
class Admin::TeamMembersController < AdminController
def edit
@admin_team_member = UsersProject.find(params[:id])
end
def update
@admin_team_member = UsersProject.find(params[:id])
if @admin_team_member.update_attributes(params[:team_member])
redirect_to [:admin, @admin_team_member.project], notice: 'Project Access was successfully updated.'
else
render action: "edit"
end
end
def destroy
@admin_team_member = UsersProject.find(params[:id])
@admin_team_member.destroy
redirect_to :back
end
end
# Provides a base class for Admin controllers to subclass
#
# Automatically sets the layout and ensures an administrator is logged in
class Admin::Teams::ApplicationController < Admin::ApplicationController
private
def user_team
@team = UserTeam.find_by_path(params[:team_id])
end
end
class Admin::Teams::MembersController < Admin::Teams::ApplicationController
def new
@users = User.potential_team_members(user_team)
@users = UserDecorator.decorate @users
end
def create
unless params[:user_ids].blank?
user_ids = params[:user_ids]
access = params[:default_project_access]
is_admin = params[:group_admin]
user_team.add_members(user_ids, access, is_admin)
end
redirect_to admin_team_path(user_team), notice: 'Members was successfully added into Team of users.'
end
def edit
team_member
end
def update
options = {default_projects_access: params[:default_project_access], group_admin: params[:group_admin]}
if user_team.update_membership(team_member, options)
redirect_to admin_team_path(user_team), notice: "Membership for #{team_member.name} was successfully updated in Team of users."
else
render :edit
end
end
def destroy
user_team.remove_member(team_member)
redirect_to admin_team_path(user_team), notice: "Member #{team_member.name} was successfully removed from Team of users."
end
protected
def team_member
@member ||= user_team.members.find_by_username(params[:id])
end
end
class Admin::Teams::ProjectsController < Admin::Teams::ApplicationController
def new
@projects = Project.scoped
@projects = @projects.without_team(user_team) if user_team.projects.any?
#@projects.reject!(&:empty_repo?)
end
def create
unless params[:project_ids].blank?
project_ids = params[:project_ids]
access = params[:greatest_project_access]
user_team.assign_to_projects(project_ids, access)
end
redirect_to admin_team_path(user_team), notice: 'Team of users was successfully assgned to projects.'
end
def edit
team_project
end
def update
if user_team.update_project_access(team_project, params[:greatest_project_access])
redirect_to admin_team_path(user_team), notice: 'Access was successfully updated.'
else
render :edit
end
end
def destroy
user_team.resign_from_project(team_project)
redirect_to admin_team_path(user_team), notice: 'Team of users was successfully reassigned from project.'
end
protected
def team_project
@project ||= user_team.projects.find_with_namespace(params[:id])
end
end
class Admin::TeamsController < Admin::ApplicationController
def index
@teams = UserTeam.order('name ASC')
@teams = @teams.search(params[:name]) if params[:name].present?
@teams = @teams.page(params[:page]).per(20)
end
def show
user_team
end
def new
@team = UserTeam.new
end
def edit
user_team
end
def create
@team = UserTeam.new(params[:user_team])
@team.path = @team.name.dup.parameterize if @team.name
@team.owner = current_user
if @team.save
redirect_to admin_team_path(@team), notice: 'Team of users was successfully created.'
else
render action: "new"
end
end
def update
user_team_params = params[:user_team].dup
owner_id = user_team_params.delete(:owner_id)
if owner_id
user_team.owner = User.find(owner_id)
end
if user_team.update_attributes(user_team_params)
redirect_to admin_team_path(user_team), notice: 'Team of users was successfully updated.'
else
render action: "edit"
end
end
def destroy
user_team.destroy
redirect_to admin_teams_path, notice: 'Team of users was successfully deleted.'
end
protected
def user_team
@team ||= UserTeam.find_by_path(params[:id])
end
end
class Admin::UsersController < AdminController class Admin::UsersController < Admin::ApplicationController
before_filter :admin_user, only: [:show, :edit, :update, :destroy]
def index def index
@admin_users = User.scoped @admin_users = User.scoped
@admin_users = @admin_users.filter(params[:filter]) @admin_users = @admin_users.filter(params[:filter])
...@@ -7,25 +9,18 @@ class Admin::UsersController < AdminController ...@@ -7,25 +9,18 @@ class Admin::UsersController < AdminController
end end
def show def show
@admin_user = User.find(params[:id]) @projects = Project.scoped
@projects = @projects.without_user(admin_user) if admin_user.authorized_projects.present?
@projects = if @admin_user.authorized_projects.empty?
Project
else
Project.without_user(@admin_user)
end.all
end end
def team_update def team_update
@admin_user = User.find(params[:id])
UsersProject.add_users_into_projects( UsersProject.add_users_into_projects(
params[:project_ids], params[:project_ids],
[@admin_user.id], [admin_user.id],
params[:project_access] params[:project_access]
) )
redirect_to [:admin, @admin_user], notice: 'Teams were successfully updated.' redirect_to [:admin, admin_user], notice: 'Teams were successfully updated.'
end end
...@@ -34,13 +29,11 @@ class Admin::UsersController < AdminController ...@@ -34,13 +29,11 @@ class Admin::UsersController < AdminController
end end
def edit def edit
@admin_user = User.find(params[:id]) admin_user
end end
def block def block
@admin_user = User.find(params[:id]) if admin_user.block
if @admin_user.block
redirect_to :back, alert: "Successfully blocked" redirect_to :back, alert: "Successfully blocked"
else else
redirect_to :back, alert: "Error occured. User was not blocked" redirect_to :back, alert: "Error occured. User was not blocked"
...@@ -48,9 +41,7 @@ class Admin::UsersController < AdminController ...@@ -48,9 +41,7 @@ class Admin::UsersController < AdminController
end end
def unblock def unblock
@admin_user = User.find(params[:id]) if admin_user.update_attribute(:blocked, false)
if @admin_user.update_attribute(:blocked, false)
redirect_to :back, alert: "Successfully unblocked" redirect_to :back, alert: "Successfully unblocked"
else else
redirect_to :back, alert: "Error occured. User was not unblocked" redirect_to :back, alert: "Error occured. User was not unblocked"
...@@ -82,30 +73,34 @@ class Admin::UsersController < AdminController ...@@ -82,30 +73,34 @@ class Admin::UsersController < AdminController
params[:user].delete(:password_confirmation) params[:user].delete(:password_confirmation)
end end
@admin_user = User.find(params[:id]) admin_user.admin = (admin && admin.to_i > 0)
@admin_user.admin = (admin && admin.to_i > 0)
respond_to do |format| respond_to do |format|
if @admin_user.update_attributes(params[:user], as: :admin) if admin_user.update_attributes(params[:user], as: :admin)
format.html { redirect_to [:admin, @admin_user], notice: 'User was successfully updated.' } format.html { redirect_to [:admin, admin_user], notice: 'User was successfully updated.' }
format.json { head :ok } format.json { head :ok }
else else
format.html { render action: "edit" } format.html { render action: "edit" }
format.json { render json: @admin_user.errors, status: :unprocessable_entity } format.json { render json: admin_user.errors, status: :unprocessable_entity }
end end
end end
end end
def destroy def destroy
@admin_user = User.find(params[:id]) if admin_user.personal_projects.count > 0
if @admin_user.personal_projects.count > 0
redirect_to admin_users_path, alert: "User is a project owner and can't be removed." and return redirect_to admin_users_path, alert: "User is a project owner and can't be removed." and return
end end
@admin_user.destroy admin_user.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to admin_users_url } format.html { redirect_to admin_users_path }
format.json { head :ok } format.json { head :ok }
end end
end end
protected
def admin_user
@admin_user ||= User.find_by_username!(params[:id])
end
end end
...@@ -94,6 +94,18 @@ class ApplicationController < ActionController::Base ...@@ -94,6 +94,18 @@ class ApplicationController < ActionController::Base
return access_denied! unless can?(current_user, :download_code, project) return access_denied! unless can?(current_user, :download_code, project)
end end
def authorize_create_team!
return access_denied! unless can?(current_user, :create_team, nil)
end
def authorize_manage_user_team!
return access_denied! unless user_team.present? && can?(current_user, :manage_user_team, user_team)
end
def authorize_admin_user_team!
return access_denied! unless user_team.present? && can?(current_user, :admin_user_team, user_team)
end
def access_denied! def access_denied!
render "errors/access_denied", layout: "errors", status: 404 render "errors/access_denied", layout: "errors", status: 404
end end
...@@ -135,4 +147,5 @@ class ApplicationController < ActionController::Base ...@@ -135,4 +147,5 @@ class ApplicationController < ActionController::Base
def dev_tools def dev_tools
Rack::MiniProfiler.authorize_request Rack::MiniProfiler.authorize_request
end end
end end
class DashboardController < ApplicationController class DashboardController < ApplicationController
respond_to :html respond_to :html
before_filter :projects before_filter :load_projects
before_filter :event_filter, only: :index before_filter :event_filter, only: :show
def index def show
@groups = current_user.authorized_groups @groups = current_user.authorized_groups
@has_authorized_projects = @projects.count > 0 @has_authorized_projects = @projects.count > 0
@teams = current_user.authorized_teams
@projects = case params[:scope] @projects_count = @projects.count
when 'personal' then @projects = @projects.limit(20)
@projects.personal(current_user)
when 'joined' then
@projects.joined(current_user)
else
@projects
end
@projects = @projects.page(params[:page]).per(30)
@events = Event.in_projects(current_user.authorized_projects.pluck(:id)) @events = Event.in_projects(current_user.authorized_projects.pluck(:id))
@events = @event_filter.apply_filter(@events) @events = @event_filter.apply_filter(@events)
...@@ -33,6 +24,19 @@ class DashboardController < ApplicationController ...@@ -33,6 +24,19 @@ class DashboardController < ApplicationController
end end
end end
def projects
@projects = case params[:scope]
when 'personal' then
@projects.personal(current_user)
when 'joined' then
@projects.joined(current_user)
else
@projects
end
@projects = @projects.page(params[:page]).per(30)
end
# Get authored or assigned open merge requests # Get authored or assigned open merge requests
def merge_requests def merge_requests
@merge_requests = current_user.cared_merge_requests @merge_requests = current_user.cared_merge_requests
...@@ -55,7 +59,7 @@ class DashboardController < ApplicationController ...@@ -55,7 +59,7 @@ class DashboardController < ApplicationController
protected protected
def projects def load_projects
@projects = current_user.authorized_projects.sorted_by_activity @projects = current_user.authorized_projects.sorted_by_activity
end end
......
class GroupsController < ApplicationController class GroupsController < ApplicationController
respond_to :html respond_to :html
layout 'group' layout 'group', except: [:new, :create]
before_filter :group before_filter :group, except: [:new, :create]
before_filter :projects
# Authorize # Authorize
before_filter :authorize_read_group! before_filter :authorize_read_group!, except: [:new, :create]
before_filter :authorize_create_group!, only: [:new, :create]
# Load group projects
before_filter :projects, except: [:new, :create]
def new
@group = Group.new
end
def create
@group = Group.new(params[:group])
@group.path = @group.name.dup.parameterize if @group.name
@group.owner = current_user
if @group.save
redirect_to @group, notice: 'Group was successfully created.'
else
render action: "new"
end
end
def show def show
@events = Event.in_projects(project_ids).limit(20).offset(params[:offset] || 0) @events = Event.in_projects(project_ids).limit(20).offset(params[:offset] || 0)
...@@ -85,4 +104,8 @@ class GroupsController < ApplicationController ...@@ -85,4 +104,8 @@ class GroupsController < ApplicationController
return render_404 return render_404
end end
end end
def authorize_create_group!
can?(current_user, :create_group, nil)
end
end end
class Projects::ApplicationController < ApplicationController
before_filter :authorize_admin_team_member!
protected
def user_team
@team ||= UserTeam.find_by_path(params[:id])
end
end
class Projects::TeamsController < Projects::ApplicationController
def available
@teams = current_user.is_admin? ? UserTeam.scoped : current_user.user_teams
@teams = @teams.without_project(project)
unless @teams.any?
redirect_to project_team_index_path(project), notice: "No avaliable teams for assigment."
end
end
def assign
unless params[:team_id].blank?
team = UserTeam.find(params[:team_id])
access = params[:greatest_project_access]
team.assign_to_project(project, access)
end
redirect_to project_team_index_path(project)
end
def resign
team = project.user_teams.find_by_path(params[:id])
team.resign_from_project(project)
redirect_to project_team_index_path(project)
end
end
...@@ -19,7 +19,7 @@ class ProjectsController < ProjectResourceController ...@@ -19,7 +19,7 @@ class ProjectsController < ProjectResourceController
end end
def create def create
@project = Projects::CreateContext.new(current_user, params[:project]).execute @project = ::Projects::CreateContext.new(current_user, params[:project]).execute
respond_to do |format| respond_to do |format|
flash[:notice] = 'Project was successfully created.' if @project.saved? flash[:notice] = 'Project was successfully created.' if @project.saved?
...@@ -35,7 +35,7 @@ class ProjectsController < ProjectResourceController ...@@ -35,7 +35,7 @@ class ProjectsController < ProjectResourceController
end end
def update def update
status = Projects::UpdateContext.new(project, current_user, params).execute status = ::Projects::UpdateContext.new(project, current_user, params).execute
respond_to do |format| respond_to do |format|
if status if status
......
class SearchController < ApplicationController class SearchController < ApplicationController
def show def show
result = SearchContext.new(current_user.authorized_projects.map(&:id), params).execute project_id = params[:project_id]
group_id = params[:group_id]
project_ids = current_user.authorized_projects.map(&:id)
if group_id.present?
group_project_ids = Group.find(group_id).projects.map(&:id)
project_ids.select! { |id| group_project_ids.include?(id)}
elsif project_id.present?
project_ids.select! { |id| id == project_id.to_i}
end
result = SearchContext.new(project_ids, params).execute
@projects = result[:projects] @projects = result[:projects]
@merge_requests = result[:merge_requests] @merge_requests = result[:merge_requests]
......
...@@ -4,15 +4,16 @@ class TeamMembersController < ProjectResourceController ...@@ -4,15 +4,16 @@ class TeamMembersController < ProjectResourceController
before_filter :authorize_admin_project!, except: [:index, :show] before_filter :authorize_admin_project!, except: [:index, :show]
def index def index
@teams = UserTeam.scoped
end end
def show def show
@team_member = project.users_projects.find(params[:id]) @user_project_relation = project.users_projects.find_by_user_id(member)
@events = @team_member.user.recent_events.where(:project_id => @project.id).limit(7) @events = member.recent_events.in_projects(project).limit(7)
end end
def new def new
@team_member = project.users_projects.new @user_project_relation = project.users_projects.new
end end
def create def create
...@@ -28,18 +29,18 @@ class TeamMembersController < ProjectResourceController ...@@ -28,18 +29,18 @@ class TeamMembersController < ProjectResourceController
end end
def update def update
@team_member = project.users_projects.find(params[:id]) @user_project_relation = project.users_projects.find_by_user_id(member)
@team_member.update_attributes(params[:team_member]) @user_project_relation.update_attributes(params[:team_member])
unless @team_member.valid? unless @user_project_relation.valid?
flash[:alert] = "User should have at least one role" flash[:alert] = "User should have at least one role"
end end
redirect_to project_team_index_path(@project) redirect_to project_team_index_path(@project)
end end
def destroy def destroy
@team_member = project.users_projects.find(params[:id]) @user_project_relation = project.users_projects.find_by_user_id(member)
@team_member.destroy @user_project_relation.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to project_team_index_path(@project) } format.html { redirect_to project_team_index_path(@project) }
...@@ -54,4 +55,10 @@ class TeamMembersController < ProjectResourceController ...@@ -54,4 +55,10 @@ class TeamMembersController < ProjectResourceController
redirect_to project_team_members_path(project), notice: notice redirect_to project_team_members_path(project), notice: notice
end end
protected
def member
@member ||= User.find_by_username(params[:id])
end
end end
class Teams::ApplicationController < ApplicationController
layout 'user_team'
before_filter :authorize_manage_user_team!
protected
def user_team
@team ||= UserTeam.find_by_path(params[:team_id])
end
end
class Teams::MembersController < Teams::ApplicationController
skip_before_filter :authorize_manage_user_team!, only: [:index]
def index
@members = user_team.members
end
def new
@users = User.potential_team_members(user_team)
@users = UserDecorator.decorate @users
end
def create
unless params[:user_ids].blank?
user_ids = params[:user_ids]
access = params[:default_project_access]
is_admin = params[:group_admin]
user_team.add_members(user_ids, access, is_admin)
end
redirect_to team_members_path(user_team), notice: 'Members was successfully added into Team of users.'
end
def edit
team_member
end
def update
options = {default_projects_access: params[:default_project_access], group_admin: params[:group_admin]}
if user_team.update_membership(team_member, options)
redirect_to team_members_path(user_team), notice: "Membership for #{team_member.name} was successfully updated in Team of users."
else
render :edit
end
end
def destroy
user_team.remove_member(team_member)
redirect_to team_path(user_team), notice: "Member #{team_member.name} was successfully removed from Team of users."
end
protected
def team_member
@member ||= user_team.members.find_by_username(params[:id])
end
end
class Teams::ProjectsController < Teams::ApplicationController
skip_before_filter :authorize_manage_user_team!, only: [:index]
def index
@projects = user_team.projects
@avaliable_projects = current_user.admin? ? Project.without_team(user_team) : current_user.owned_projects.without_team(user_team)
end
def new
user_team
@avaliable_projects = current_user.owned_projects.scoped
@avaliable_projects = @avaliable_projects.without_team(user_team) if user_team.projects.any?
redirect_to team_projects_path(user_team), notice: "No avalible projects." unless @avaliable_projects.any?
end
def create
redirect_to :back if params[:project_ids].blank?
project_ids = params[:project_ids]
access = params[:greatest_project_access]
# Reject non-allowed projects
allowed_project_ids = current_user.owned_projects.map(&:id)
project_ids.select! { |id| allowed_project_ids.include?(id.to_i) }
# Assign projects to team
user_team.assign_to_projects(project_ids, access)
redirect_to team_projects_path(user_team), notice: 'Team of users was successfully assigned to projects.'
end
def edit
team_project
end
def update
if user_team.update_project_access(team_project, params[:greatest_project_access])
redirect_to team_projects_path(user_team), notice: 'Access was successfully updated.'
else
render :edit
end
end
def destroy
user_team.resign_from_project(team_project)
redirect_to team_projects_path(user_team), notice: 'Team of users was successfully reassigned from project.'
end
private
def team_project
@project ||= user_team.projects.find_with_namespace(params[:id])
end
end
class TeamsController < ApplicationController
# Authorize
before_filter :authorize_create_team!, only: [:new, :create]
before_filter :authorize_manage_user_team!, only: [:edit, :update]
before_filter :authorize_admin_user_team!, only: [:destroy]
before_filter :user_team, except: [:new, :create]
layout 'user_team', except: [:new, :create]
def show
user_team
projects
@events = Event.in_projects(user_team.project_ids).limit(20).offset(params[:offset] || 0)
end
def edit
user_team
end
def update
if user_team.update_attributes(params[:user_team])
redirect_to team_path(user_team)
else
render action: :edit
end
end
def destroy
user_team.destroy
redirect_to dashboard_path
end
def new
@team = UserTeam.new
end
def create
@team = UserTeam.new(params[:user_team])
@team.owner = current_user unless params[:owner]
@team.path = @team.name.dup.parameterize if @team.name
if @team.save
redirect_to team_path(@team)
else
render action: :new
end
end
# Get authored or assigned open merge requests
def merge_requests
projects
@merge_requests = MergeRequest.of_user_team(user_team)
@merge_requests = FilterContext.new(@merge_requests, params).execute
@merge_requests = @merge_requests.recent.page(params[:page]).per(20)
end
# Get only assigned issues
def issues
projects
@issues = Issue.of_user_team(user_team)
@issues = FilterContext.new(@issues, params).execute
@issues = @issues.recent.page(params[:page]).per(20)
@issues = @issues.includes(:author, :project)
end
protected
def projects
@projects ||= user_team.projects.sorted_by_activity
end
def user_team
@team ||= current_user.authorized_teams.find_by_path(params[:id])
end
end
...@@ -8,4 +8,8 @@ class UserDecorator < ApplicationDecorator ...@@ -8,4 +8,8 @@ class UserDecorator < ApplicationDecorator
def tm_of(project) def tm_of(project)
project.team_member_by_id(self.id) project.team_member_by_id(self.id)
end end
def name_with_email
"#{name} (#{email})"
end
end end
module Admin::Teams::MembersHelper
def member_since(team, member)
team.user_team_user_relationships.find_by_user_id(member).created_at
end
end
module Admin::Teams::ProjectsHelper
def assigned_since(team, project)
team.user_team_project_relationships.find_by_project_id(project).created_at
end
end
...@@ -72,8 +72,9 @@ module ApplicationHelper ...@@ -72,8 +72,9 @@ module ApplicationHelper
end end
def search_autocomplete_source def search_autocomplete_source
projects = current_user.authorized_projects.map { |p| { label: p.name_with_namespace, url: project_path(p) } } projects = current_user.authorized_projects.map { |p| { label: "project: #{p.name_with_namespace}", url: project_path(p) } }
groups = current_user.authorized_groups.map { |group| { label: "<group> #{group.name}", url: group_path(group) } } groups = current_user.authorized_groups.map { |group| { label: "group: #{group.name}", url: group_path(group) } }
teams = current_user.authorized_teams.map { |team| { label: "team: #{team.name}", url: team_path(team) } }
default_nav = [ default_nav = [
{ label: "My Profile", url: profile_path }, { label: "My Profile", url: profile_path },
...@@ -83,29 +84,29 @@ module ApplicationHelper ...@@ -83,29 +84,29 @@ module ApplicationHelper
] ]
help_nav = [ help_nav = [
{ label: "API Help", url: help_api_path }, { label: "help: API Help", url: help_api_path },
{ label: "Markdown Help", url: help_markdown_path }, { label: "help: Markdown Help", url: help_markdown_path },
{ label: "Permissions Help", url: help_permissions_path }, { label: "help: Permissions Help", url: help_permissions_path },
{ label: "Public Access Help", url: help_public_access_path }, { label: "help: Public Access Help", url: help_public_access_path },
{ label: "Rake Tasks Help", url: help_raketasks_path }, { label: "help: Rake Tasks Help", url: help_raketasks_path },
{ label: "SSH Keys Help", url: help_ssh_path }, { label: "help: SSH Keys Help", url: help_ssh_path },
{ label: "System Hooks Help", url: help_system_hooks_path }, { label: "help: System Hooks Help", url: help_system_hooks_path },
{ label: "Web Hooks Help", url: help_web_hooks_path }, { label: "help: Web Hooks Help", url: help_web_hooks_path },
{ label: "Workflow Help", url: help_workflow_path }, { label: "help: Workflow Help", url: help_workflow_path },
] ]
project_nav = [] project_nav = []
if @project && @project.repository && @project.repository.root_ref if @project && @project.repository && @project.repository.root_ref
project_nav = [ project_nav = [
{ label: "#{@project.name} Issues", url: project_issues_path(@project) }, { label: "#{@project.name_with_namespace} - Issues", url: project_issues_path(@project) },
{ label: "#{@project.name} Commits", url: project_commits_path(@project, @ref || @project.repository.root_ref) }, { label: "#{@project.name_with_namespace} - Commits", url: project_commits_path(@project, @ref || @project.repository.root_ref) },
{ label: "#{@project.name} Merge Requests", url: project_merge_requests_path(@project) }, { label: "#{@project.name_with_namespace} - Merge Requests", url: project_merge_requests_path(@project) },
{ label: "#{@project.name} Milestones", url: project_milestones_path(@project) }, { label: "#{@project.name_with_namespace} - Milestones", url: project_milestones_path(@project) },
{ label: "#{@project.name} Snippets", url: project_snippets_path(@project) }, { label: "#{@project.name_with_namespace} - Snippets", url: project_snippets_path(@project) },
{ label: "#{@project.name} Team", url: project_team_index_path(@project) }, { label: "#{@project.name_with_namespace} - Team", url: project_team_index_path(@project) },
{ label: "#{@project.name} Tree", url: project_tree_path(@project, @ref || @project.repository.root_ref) }, { label: "#{@project.name_with_namespace} - Tree", url: project_tree_path(@project, @ref || @project.repository.root_ref) },
{ label: "#{@project.name} Wall", url: wall_project_path(@project) }, { label: "#{@project.name_with_namespace} - Wall", url: wall_project_path(@project) },
{ label: "#{@project.name} Wiki", url: project_wikis_path(@project) }, { label: "#{@project.name_with_namespace} - Wiki", url: project_wikis_path(@project) },
] ]
end end
......
...@@ -59,9 +59,9 @@ module CommitsHelper ...@@ -59,9 +59,9 @@ module CommitsHelper
def image_diff_class(diff) def image_diff_class(diff)
if diff.deleted_file if diff.deleted_file
"diff_removed" "deleted"
elsif diff.new_file elsif diff.new_file
"diff_added" "added"
else else
nil nil
end end
......
...@@ -9,9 +9,9 @@ module DashboardHelper ...@@ -9,9 +9,9 @@ module DashboardHelper
case entity case entity
when 'issue' then when 'issue' then
dashboard_issues_path(options) issues_dashboard_path(options)
when 'merge_request' when 'merge_request'
dashboard_merge_requests_path(options) merge_requests_dashboard_path(options)
end end
end end
......
...@@ -3,8 +3,12 @@ module ProjectsHelper ...@@ -3,8 +3,12 @@ module ProjectsHelper
@project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access) @project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access)
end end
def remove_from_team_message(project, member) def grouper_project_teams(project)
"You are going to remove #{member.user_name} from #{project.name}. Are you sure?" @project.user_team_project_relationships.sort_by(&:greatest_access).reverse.group_by(&:greatest_access)
end
def remove_from_project_team_message(project, user)
"You are going to remove #{user.name} from #{project.name} project team. Are you sure?"
end end
def link_to_project project def link_to_project project
...@@ -51,7 +55,9 @@ module ProjectsHelper ...@@ -51,7 +55,9 @@ module ProjectsHelper
def project_title project def project_title project
if project.group if project.group
project.name_with_namespace content_tag :span do
link_to(project.group.name, group_path(project.group)) + " / " + project.name
end
else else
project.name project.name
end end
......
...@@ -39,7 +39,12 @@ module TabHelper ...@@ -39,7 +39,12 @@ module TabHelper
# Returns a list item element String # Returns a list item element String
def nav_link(options = {}, &block) def nav_link(options = {}, &block)
if path = options.delete(:path) if path = options.delete(:path)
if path.respond_to?(:each)
c = path.map { |p| p.split('#').first }
a = path.map { |p| p.split('#').last }
else
c, a, _ = path.split('#') c, a, _ = path.split('#')
end
else else
c = options.delete(:controller) c = options.delete(:controller)
a = options.delete(:action) a = options.delete(:action)
......
module UserTeamsHelper
def team_filter_path(entity, options={})
exist_opts = {
status: params[:status],
project_id: params[:project_id],
}
options = exist_opts.merge(options)
case entity
when 'issue' then
issues_team_path(@team, options)
when 'merge_request'
merge_requests_team_path(@team, options)
end
end
def grouped_user_team_members(team)
team.user_team_user_relationships.sort_by(&:permission).reverse.group_by(&:permission)
end
def remove_from_user_team_message(team, member)
"You are going to remove #{member.name} from #{team.name}. Are you sure?"
end
end
class Ability class Ability
class << self class << self
def allowed(object, subject) def allowed(user, subject)
return [] unless user.kind_of?(User)
case subject.class.name case subject.class.name
when "Project" then project_abilities(object, subject) when "Project" then project_abilities(user, subject)
when "Issue" then issue_abilities(object, subject) when "Issue" then issue_abilities(user, subject)
when "Note" then note_abilities(object, subject) when "Note" then note_abilities(user, subject)
when "Snippet" then snippet_abilities(object, subject) when "Snippet" then snippet_abilities(user, subject)
when "MergeRequest" then merge_request_abilities(object, subject) when "MergeRequest" then merge_request_abilities(user, subject)
when "Group", "Namespace" then group_abilities(object, subject) when "Group", "Namespace" then group_abilities(user, subject)
when "UserTeam" then user_team_abilities(user, subject)
else [] else []
end.concat(global_abilities(user))
end end
def global_abilities(user)
rules = []
rules << :create_group if user.can_create_group
rules << :create_team if user.can_create_team
rules
end end
def project_abilities(user, project) def project_abilities(user, project)
...@@ -110,6 +120,22 @@ class Ability ...@@ -110,6 +120,22 @@ class Ability
rules.flatten rules.flatten
end end
def user_team_abilities user, team
rules = []
# Only group owner and administrators can manage group
if team.owner == user || team.admin?(user) || user.admin?
rules << [ :manage_user_team ]
end
if team.owner == user || user.admin?
rules << [ :admin_user_team ]
end
rules.flatten
end
[:issue, :note, :snippet, :merge_request].each do |name| [:issue, :note, :snippet, :merge_request].each do |name|
define_method "#{name}_abilities" do |user, subject| define_method "#{name}_abilities" do |user, subject|
if subject.author == user if subject.author == user
......
...@@ -22,6 +22,7 @@ module Issuable ...@@ -22,6 +22,7 @@ module Issuable
scope :opened, where(closed: false) scope :opened, where(closed: false)
scope :closed, where(closed: true) scope :closed, where(closed: true)
scope :of_group, ->(group) { where(project_id: group.project_ids) } scope :of_group, ->(group) { where(project_id: group.project_ids) }
scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }
scope :assigned, ->(u) { where(assignee_id: u.id)} scope :assigned, ->(u) { where(assignee_id: u.id)}
scope :recent, order("created_at DESC") scope :recent, order("created_at DESC")
......
...@@ -33,14 +33,13 @@ class Project < ActiveRecord::Base ...@@ -33,14 +33,13 @@ class Project < ActiveRecord::Base
attr_accessor :error_code attr_accessor :error_code
# Relations # Relations
belongs_to :creator, foreign_key: "creator_id", class_name: "User"
belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'" belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'"
belongs_to :namespace belongs_to :namespace
belongs_to :creator, has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', foreign_key: 'project_id'
class_name: "User", has_one :gitlab_ci_service, dependent: :destroy
foreign_key: "creator_id"
has_many :users, through: :users_projects
has_many :events, dependent: :destroy has_many :events, dependent: :destroy
has_many :merge_requests, dependent: :destroy has_many :merge_requests, dependent: :destroy
has_many :issues, dependent: :destroy, order: "closed, created_at DESC" has_many :issues, dependent: :destroy, order: "closed, created_at DESC"
...@@ -48,12 +47,16 @@ class Project < ActiveRecord::Base ...@@ -48,12 +47,16 @@ class Project < ActiveRecord::Base
has_many :users_projects, dependent: :destroy has_many :users_projects, dependent: :destroy
has_many :notes, dependent: :destroy has_many :notes, dependent: :destroy
has_many :snippets, dependent: :destroy has_many :snippets, dependent: :destroy
has_many :deploy_keys, dependent: :destroy, foreign_key: "project_id", class_name: "Key" has_many :deploy_keys, dependent: :destroy, class_name: "Key", foreign_key: "project_id"
has_many :hooks, dependent: :destroy, class_name: "ProjectHook" has_many :hooks, dependent: :destroy, class_name: "ProjectHook"
has_many :wikis, dependent: :destroy has_many :wikis, dependent: :destroy
has_many :protected_branches, dependent: :destroy has_many :protected_branches, dependent: :destroy
has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', foreign_key: 'project_id' has_many :user_team_project_relationships, dependent: :destroy
has_one :gitlab_ci_service, dependent: :destroy
has_many :users, through: :users_projects
has_many :user_teams, through: :user_team_project_relationships
has_many :user_team_user_relationships, through: :user_teams
has_many :user_teams_members, through: :user_team_user_relationships
delegate :name, to: :owner, allow_nil: true, prefix: true delegate :name, to: :owner, allow_nil: true, prefix: true
...@@ -77,6 +80,8 @@ class Project < ActiveRecord::Base ...@@ -77,6 +80,8 @@ class Project < ActiveRecord::Base
# Scopes # Scopes
scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) } scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) }
scope :not_in_group, ->(group) { where("id NOT IN (:ids)", ids: group.project_ids ) } scope :not_in_group, ->(group) { where("id NOT IN (:ids)", ids: group.project_ids ) }
scope :without_team, ->(team) { team.projects.present? ? where("id NOT IN (:ids)", ids: team.projects.map(&:id)) : scoped }
scope :in_team, ->(team) { where("id IN (:ids)", ids: team.projects.map(&:id)) }
scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) } scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) }
scope :sorted_by_activity, ->() { order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") } scope :sorted_by_activity, ->() { order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) } scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
...@@ -122,7 +127,7 @@ class Project < ActiveRecord::Base ...@@ -122,7 +127,7 @@ class Project < ActiveRecord::Base
end end
def team def team
@team ||= Team.new(self) @team ||= ProjectTeam.new(self)
end end
def repository def repository
...@@ -335,7 +340,7 @@ class Project < ActiveRecord::Base ...@@ -335,7 +340,7 @@ class Project < ActiveRecord::Base
end end
def execute_hooks(data) def execute_hooks(data)
hooks.each { |hook| hook.execute(data) } hooks.each { |hook| hook.async_execute(data) }
end end
def execute_services(data) def execute_services(data)
...@@ -489,6 +494,11 @@ class Project < ActiveRecord::Base ...@@ -489,6 +494,11 @@ class Project < ActiveRecord::Base
http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
end end
def project_access_human(member)
project_user_relation = self.users_projects.find_by_user_id(member.id)
self.class.access_options.key(project_user_relation.project_access)
end
# Check if current branch name is marked as protected in the system # Check if current branch name is marked as protected in the system
def protected_branch? branch_name def protected_branch? branch_name
protected_branches.map(&:name).include?(branch_name) protected_branches.map(&:name).include?(branch_name)
......
class Team class ProjectTeam
attr_accessor :project attr_accessor :project
def initialize(project) def initialize(project)
......
...@@ -40,23 +40,32 @@ class User < ActiveRecord::Base ...@@ -40,23 +40,32 @@ class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username, attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username,
:skype, :linkedin, :twitter, :dark_scheme, :theme_id, :force_random_password, :skype, :linkedin, :twitter, :dark_scheme, :theme_id, :force_random_password,
:extern_uid, :provider, as: [:default, :admin] :extern_uid, :provider, as: [:default, :admin]
attr_accessible :projects_limit, as: :admin attr_accessible :projects_limit, :can_create_team, :can_create_group, as: :admin
attr_accessor :force_random_password attr_accessor :force_random_password
# Namespace for personal projects # Namespace for personal projects
has_one :namespace, class_name: "Namespace", foreign_key: :owner_id, conditions: 'type IS NULL', dependent: :destroy has_one :namespace, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace", conditions: 'type IS NULL'
has_many :groups, class_name: "Group", foreign_key: :owner_id
has_many :keys, dependent: :destroy has_many :keys, dependent: :destroy
has_many :users_projects, dependent: :destroy has_many :users_projects, dependent: :destroy
has_many :issues, foreign_key: :author_id, dependent: :destroy has_many :issues, dependent: :destroy, foreign_key: :author_id
has_many :notes, foreign_key: :author_id, dependent: :destroy has_many :notes, dependent: :destroy, foreign_key: :author_id
has_many :merge_requests, foreign_key: :author_id, dependent: :destroy has_many :merge_requests, dependent: :destroy, foreign_key: :author_id
has_many :events, class_name: "Event", foreign_key: :author_id, dependent: :destroy has_many :events, dependent: :destroy, foreign_key: :author_id, class_name: "Event"
has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue"
has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
has_many :groups, class_name: "Group", foreign_key: :owner_id
has_many :recent_events, class_name: "Event", foreign_key: :author_id, order: "id DESC" has_many :recent_events, class_name: "Event", foreign_key: :author_id, order: "id DESC"
has_many :assigned_issues, class_name: "Issue", foreign_key: :assignee_id, dependent: :destroy
has_many :assigned_merge_requests, class_name: "MergeRequest", foreign_key: :assignee_id, dependent: :destroy has_many :projects, through: :users_projects
has_many :user_team_user_relationships, dependent: :destroy
has_many :user_teams, through: :user_team_user_relationships
has_many :user_team_project_relationships, through: :user_teams
has_many :team_projects, through: :user_team_project_relationships
validates :name, presence: true validates :name, presence: true
validates :bio, length: { within: 0..255 } validates :bio, length: { within: 0..255 }
...@@ -80,6 +89,9 @@ class User < ActiveRecord::Base ...@@ -80,6 +89,9 @@ class User < ActiveRecord::Base
scope :blocked, where(blocked: true) scope :blocked, where(blocked: true)
scope :active, where(blocked: false) scope :active, where(blocked: false)
scope :alphabetically, order('name ASC') scope :alphabetically, order('name ASC')
scope :in_team, ->(team){ where(id: team.member_ids) }
scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active }
# #
# Class methods # Class methods
...@@ -131,6 +143,11 @@ class User < ActiveRecord::Base ...@@ -131,6 +143,11 @@ class User < ActiveRecord::Base
# #
# Instance methods # Instance methods
# #
def to_param
username
end
def generate_password def generate_password
if self.force_random_password if self.force_random_password
self.password = self.password_confirmation = Devise.friendly_token.first(8) self.password = self.password_confirmation = Devise.friendly_token.first(8)
...@@ -220,7 +237,7 @@ class User < ActiveRecord::Base ...@@ -220,7 +237,7 @@ class User < ActiveRecord::Base
end end
def can_create_group? def can_create_group?
is_admin? can?(:create_group, nil)
end end
def abilities def abilities
...@@ -283,4 +300,15 @@ class User < ActiveRecord::Base ...@@ -283,4 +300,15 @@ class User < ActiveRecord::Base
def namespace_id def namespace_id
namespace.try :id namespace.try :id
end end
def authorized_teams
@authorized_teams ||= begin
ids = []
ids << UserTeam.with_member(self).pluck('user_teams.id')
ids << UserTeam.created_by(self).pluck('user_teams.id')
ids.flatten
UserTeam.where(id: ids)
end
end
end end
class UserTeam < ActiveRecord::Base
attr_accessible :name, :owner_id, :path
belongs_to :owner, class_name: User
has_many :user_team_project_relationships, dependent: :destroy
has_many :user_team_user_relationships, dependent: :destroy
has_many :projects, through: :user_team_project_relationships
has_many :members, through: :user_team_user_relationships, source: :user
validates :name, presence: true, uniqueness: true
validates :owner, presence: true
validates :path, uniqueness: true, presence: true, length: { within: 1..255 },
format: { with: Gitlab::Regex.path_regex,
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
scope :with_member, ->(user){ joins(:user_team_user_relationships).where(user_team_user_relationships: {user_id: user.id}) }
scope :with_project, ->(project){ joins(:user_team_project_relationships).where(user_team_project_relationships: {project_id: project})}
scope :without_project, ->(project){ where("user_teams.id NOT IN (:ids)", ids: (a = with_project(project); a.blank? ? 0 : a))}
scope :created_by, ->(user){ where(owner_id: user) }
class << self
def search query
where("name LIKE :query OR path LIKE :query", query: "%#{query}%")
end
def global_id
'GLN'
end
def access_roles
UsersProject.access_roles
end
end
def to_param
path
end
def assign_to_projects(projects, access)
projects.each do |project|
assign_to_project(project, access)
end
end
def assign_to_project(project, access)
Gitlab::UserTeamManager.assign(self, project, access)
end
def resign_from_project(project)
Gitlab::UserTeamManager.resign(self, project)
end
def add_members(users, access, group_admin)
users.each do |user|
add_member(user, access, group_admin)
end
end
def add_member(user, access, group_admin)
Gitlab::UserTeamManager.add_member_into_team(self, user, access, group_admin)
end
def remove_member(user)
Gitlab::UserTeamManager.remove_member_from_team(self, user)
end
def update_membership(user, options)
Gitlab::UserTeamManager.update_team_user_membership(self, user, options)
end
def update_project_access(project, permission)
Gitlab::UserTeamManager.update_project_greates_access(self, project, permission)
end
def max_project_access(project)
user_team_project_relationships.find_by_project_id(project).greatest_access
end
def human_max_project_access(project)
self.class.access_roles.invert[max_project_access(project)]
end
def default_projects_access(member)
user_team_user_relationships.find_by_user_id(member).permission
end
def human_default_projects_access(member)
self.class.access_roles.invert[default_projects_access(member)]
end
def admin?(member)
user_team_user_relationships.with_user(member).first.group_admin?
end
end
class UserTeamProjectRelationship < ActiveRecord::Base
attr_accessible :greatest_access, :project_id, :user_team_id
belongs_to :user_team
belongs_to :project
validates :project, presence: true
validates :user_team, presence: true
validate :check_greatest_access
scope :with_project, ->(project){ where(project_id: project.id) }
def team_name
user_team.name
end
private
def check_greatest_access
errors.add(:base, :incorrect_access_code) unless correct_access?
end
def correct_access?
return false if greatest_access.blank?
return true if UsersProject.access_roles.has_value?(greatest_access)
false
end
end
class UserTeamUserRelationship < ActiveRecord::Base
attr_accessible :group_admin, :permission, :user_id, :user_team_id
belongs_to :user_team
belongs_to :user
validates :user_team, presence: true
validates :user, presence: true
scope :with_user, ->(user) { where(user_id: user.id) }
def user_name
user.name
end
def access_human
UsersProject.access_roles.invert[permission]
end
end
...@@ -39,7 +39,10 @@ class UsersProject < ActiveRecord::Base ...@@ -39,7 +39,10 @@ class UsersProject < ActiveRecord::Base
scope :reporters, where(project_access: REPORTER) scope :reporters, where(project_access: REPORTER)
scope :developers, where(project_access: DEVELOPER) scope :developers, where(project_access: DEVELOPER)
scope :masters, where(project_access: MASTER) scope :masters, where(project_access: MASTER)
scope :in_project, ->(project) { where(project_id: project.id) } scope :in_project, ->(project) { where(project_id: project.id) }
scope :in_projects, ->(projects) { where(project_id: project_ids) }
scope :with_user, ->(user) { where(user_id: user.id) }
class << self class << self
......
...@@ -34,4 +34,8 @@ class WebHook < ActiveRecord::Base ...@@ -34,4 +34,8 @@ class WebHook < ActiveRecord::Base
basic_auth: {username: parsed_url.user, password: parsed_url.password}) basic_auth: {username: parsed_url.user, password: parsed_url.password})
end end
end end
def async_execute(data)
Sidekiq::Client.enqueue(ProjectWebHookWorker, id, data)
end
end end
...@@ -72,16 +72,17 @@ ...@@ -72,16 +72,17 @@
%th Users %th Users
%th Project Access: %th Project Access:
- @group.users.each do |u| - @group.users.each do |user|
%tr{class: "user_#{u.id}"} - next unless user
%td.name= link_to u.name, admin_user_path(u) %tr{class: "user_#{user.id}"}
%td.name= link_to user.name, admin_user_path(user)
%td.projects_access %td.projects_access
- u.authorized_projects.in_namespace(@group).each do |project| - user.authorized_projects.in_namespace(@group).each do |project|
- u_p = u.users_projects.in_project(project).first - u_p = user.users_projects.in_project(project).first
- next unless u_p - next unless u_p
%span %span
= project.name = project.name_with_namespace
= link_to "(#{ u_p.project_access_human })", edit_admin_team_member_path(u_p) = link_to "(#{ u_p.project_access_human })", edit_admin_project_member_path(project, user)
%tr %tr
%td.input= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5' %td.input= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
%td= select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3"} %td= select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3"}
......
= form_for @admin_team_member, as: :team_member, url: admin_team_member_path(@admin_team_member) do |f| = form_for @team_member_relation, as: :team_member, url: admin_project_member_path(@project, @member) do |f|
-if @admin_team_member.errors.any? -if @team_member_relation.errors.any?
.alert-message.block-message.error .alert-message.block-message.error
%ul %ul
- @admin_team_member.errors.full_messages.each do |msg| - @team_member_relation.errors.full_messages.each do |msg|
%li= msg %li= msg
.clearfix .clearfix
%label Project Access: %label Project Access:
.input .input
= f.select :project_access, options_for_select(Project.access_options, @admin_team_member.project_access), {}, class: "project-access-select chosen span3" = f.select :project_access, options_for_select(Project.access_options, @team_member_relation.project_access), {}, class: "project-access-select chosen span3"
%br %br
.actions .actions
......
%p.slead
Edit access for
= link_to @member.name, admin_user_path(@member)
in
= link_to @project.name_with_namespace, admin_project_path(@project)
%hr
= render 'form'
...@@ -114,9 +114,9 @@ ...@@ -114,9 +114,9 @@
%h5 %h5
Team Team
%small %small
(#{@project.users_projects.count}) (#{@project.users.count})
%br %br
%table.zebra-striped %table.zebra-striped.team_members
%thead %thead
%tr %tr
%th Name %th Name
...@@ -124,13 +124,13 @@ ...@@ -124,13 +124,13 @@
%th Repository Access %th Repository Access
%th %th
- @project.users_projects.each do |tm| - @project.users.each do |tm|
%tr %tr
%td %td
= link_to tm.user_name, admin_user_path(tm.user) = link_to tm.name, admin_user_path(tm)
%td= tm.project_access_human %td= @project.project_access_human(tm)
%td= link_to 'Edit Access', edit_admin_team_member_path(tm), class: "btn small" %td= link_to 'Edit Access', edit_admin_project_member_path(@project, tm), class: "btn small"
%td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn danger small" %td= link_to 'Remove from team', admin_project_member_path(@project, tm), confirm: 'Are you sure?', method: :delete, class: "btn danger small"
%br %br
%h5 Add new team member %h5 Add new team member
......
%p.slead
Edit access for
= link_to @admin_team_member.user_name, admin_user_path(@admin_team_member)
in
= link_to @admin_team_member.project.name_with_namespace, admin_project_path(@admin_team_member)
%hr
= render 'form'
%h3.page_title Rename Team
%hr
= form_for @team, url: admin_team_path(@team), method: :put do |f|
- if @team.errors.any?
.alert-message.block-message.error
%span= @team.errors.full_messages.first
.clearfix.team_name_holder
= f.label :name do
Team name is
.input
= f.text_field :name, placeholder: "Example Team", class: "xxlarge"
.clearfix.team_name_holder
= f.label :path do
%span.cred Team path is
.input
= f.text_field :path, placeholder: "example-team", class: "xxlarge danger"
%ul.cred
%li It will change web url for access team and team projects.
.form-actions
= f.submit 'Rename team', class: "btn danger"
= link_to 'Cancel', admin_teams_path, class: "btn cancel-btn"
%h3.page_title
Teams
%small
simple Teams description
= link_to 'New Team', new_admin_team_path, class: "btn small right"
%br
= form_tag admin_teams_path, method: :get, class: 'form-inline' do
= text_field_tag :name, params[:name], class: "xlarge"
= submit_tag "Search", class: "btn submit primary"
%table
%thead
%tr
%th
Name
%i.icon-sort-down
%th Path
%th Projects
%th Members
%th Owner
%th.cred Danger Zone!
- @teams.each do |team|
%tr
%td
%strong= link_to team.name, admin_team_path(team)
%td= team.path
%td= team.projects.count
%td= team.members.count
%td
= link_to team.owner.name, admin_user_path(team.owner_id)
%td.bgred
= link_to 'Rename', edit_admin_team_path(team), id: "edit_#{dom_id(team)}", class: "btn small"
= link_to 'Destroy', admin_team_path(team), confirm: "REMOVE #{team.name}? Are you sure?", method: :delete, class: "btn small danger"
= paginate @teams, theme: "admin"
= form_tag admin_team_member_path(@team, @member), method: :put do
-if @member.errors.any?
.alert-message.block-message.error
%ul
- @member.errors.full_messages.each do |msg|
%li= msg
.clearfix
%label Default access for Team projects:
.input
= select_tag :default_project_access, options_for_select(UserTeam.access_roles, @team.default_projects_access(@member)), class: "project-access-select chosen span3"
.clearfix
%label Team admin?
.input
= check_box_tag :group_admin, true, @team.admin?(@member)
%br
.actions
= submit_tag 'Save', class: "btn primary"
= link_to 'Cancel', :back, class: "btn"
%h3
Edit access #{@member.name} in #{@team.name} team
%hr
%table.zebra-striped
%tr
%td User:
%td= @member.name
%tr
%td Team:
%td= @team.name
%tr
%td Since:
%td= member_since(@team, @member).stamp("Nov 11, 2010")
= render 'form'
%h3.page_title
Team: #{@team.name}
%fieldset
%legend Members (#{@team.members.count})
= form_tag admin_team_members_path(@team), id: "team_members", class: "bulk_import", method: :post do
%table#members_list
%thead
%tr
%th User name
%th Default project access
%th Team access
%th
- @team.members.each do |member|
%tr.member
%td
= link_to [:admin, member] do
= member.name
%small= "(#{member.email})"
%td= @team.human_default_projects_access(member)
%td= @team.admin?(member) ? "Admin" : "Member"
%td
%tr
%td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name_with_email), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
%td= select_tag :default_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" }
%td
%span= check_box_tag :group_admin
%span Admin?
%td= submit_tag 'Add', class: "btn primary", id: :add_members_to_team
%h3.page_title New Team
%hr
= form_for @team, url: admin_teams_path do |f|
- if @team.errors.any?
.alert-message.block-message.error
%span= @team.errors.full_messages.first
.clearfix
= f.label :name do
Team name is
.input
= f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left"
&nbsp;
= f.submit 'Create team', class: "btn primary"
%hr
.padded
%ul
%li All created teams are public (users can view who enter into team and which project are assigned for this team)
%li People within a team see only projects they have access to
%li You will be able to assign existing projects for team
= form_tag admin_team_project_path(@team, @project), method: :put do
-if @project.errors.any?
.alert-message.block-message.error
%ul
- @project.errors.full_messages.each do |msg|
%li= msg
.clearfix
%label Max access for Team members:
.input
= select_tag :greatest_project_access, options_for_select(UserTeam.access_roles, @team.max_project_access(@project)), class: "project-access-select chosen span3"
%br
.actions
= submit_tag 'Save', class: "btn primary"
= link_to 'Cancel', :back, class: "btn"
%h3
Edit max access in #{@project.name} for #{@team.name} team
%hr
%table.zebra-striped
%tr
%td Project:
%td= @project.name
%tr
%td Team:
%td= @team.name
%tr
%td Since:
%td= assigned_since(@team, @project).stamp("Nov 11, 2010")
= render 'form'
%h3.page_title
Team: #{@team.name}
%fieldset
%legend Projects (#{@team.projects.count})
= form_tag admin_team_projects_path(@team), id: "assign_projects", class: "bulk_import", method: :post do
%table#projects_list
%thead
%tr
%th Project name
%th Max access
%th
- @team.projects.each do |project|
%tr.project
%td
= link_to project.name_with_namespace, [:admin, project]
%td
%span= @team.human_max_project_access(project)
%td
%tr
%td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5'
%td= select_tag :greatest_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" }
%td= submit_tag 'Add', class: "btn primary", id: :assign_projects_to_team
%h3.page_title
Team: #{@team.name}
%br
%table.zebra-striped
%thead
%tr
%th Team
%th
%tr
%td
%b
Name:
%td
= @team.name
&nbsp;
= link_to edit_admin_team_path(@team), class: "btn btn-small right" do
%i.icon-edit
Rename
%tr
%td
%b
Owner:
%td
= @team.owner.name
.right
= link_to "#", class: "btn btn-small change-owner-link" do
%i.icon-edit
Change owner
%tr.change-owner-holder.hide
%td.bgred
%b.cred
New Owner:
%td.bgred
= form_for @team, url: admin_team_path(@team) do |f|
= f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'}
%div
= f.submit 'Change Owner', class: "btn danger"
= link_to "Cancel", "#", class: "btn change-owner-cancel-link"
%fieldset
%legend
Members (#{@team.members.count})
%span= link_to 'Add members', new_admin_team_member_path(@team), class: "btn success small right", id: :add_members_to_team
- if @team.members.any?
%table#members_list
%thead
%tr
%th User name
%th Default project access
%th Team access
%th.cred.span3 Danger Zone!
- @team.members.each do |member|
%tr.member{ class: "user_#{member.id}"}
%td
= link_to [:admin, member] do
= member.name
%small= "(#{member.email})"
%td= @team.human_default_projects_access(member)
%td= @team.admin?(member) ? "Admin" : "Member"
%td.bgred
= link_to 'Edit', edit_admin_team_member_path(@team, member), class: "btn small"
&nbsp;
= 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}"
%fieldset
%legend
Projects (#{@team.projects.count})
%span= link_to 'Add projects', new_admin_team_project_path(@team), class: "btn success small right", id: :assign_projects_to_team
- if @team.projects.any?
%table#projects_list
%thead
%tr
%th Project name
%th Max access
%th.cred.span3 Danger Zone!
- @team.projects.each do |project|
%tr.project
%td
= link_to project.name_with_namespace, [:admin, project]
%td
%span= @team.human_max_project_access(project)
%td.bgred
= link_to 'Edit', edit_admin_team_project_path(@team, project), class: "btn small"
&nbsp;
= 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}"
:javascript
$(function(){
var modal = $('.change-owner-holder');
$('.change-owner-link').bind("click", function(){
$(this).hide();
modal.show();
});
$('.change-owner-cancel-link').bind("click", function(){
modal.hide();
$('.change-owner-link').show();
})
})
...@@ -46,6 +46,14 @@ ...@@ -46,6 +46,14 @@
= f.label :projects_limit = f.label :projects_limit
.input= f.number_field :projects_limit .input= f.number_field :projects_limit
.clearfix
= f.label :can_create_group
.input= f.check_box :can_create_group
.clearfix
= f.label :can_create_team
.input= f.check_box :can_create_team
.clearfix .clearfix
= f.label :admin do = f.label :admin do
%strong.cred Administrator %strong.cred Administrator
......
...@@ -123,5 +123,5 @@ ...@@ -123,5 +123,5 @@
%tr %tr
%td= link_to project.name_with_namespace, admin_project_path(project) %td= link_to project.name_with_namespace, admin_project_path(project)
%td= tm.project_access_human %td= tm.project_access_human
%td= link_to 'Edit Access', edit_admin_team_member_path(tm), class: "btn small" %td= link_to 'Edit Access', edit_admin_project_member_path(project, tm.user), class: "btn small"
%td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn small danger" %td= link_to 'Remove from team', admin_project_member_path(project, tm.user), confirm: 'Are you sure?', method: :delete, class: "btn small danger"
...@@ -11,19 +11,7 @@ ...@@ -11,19 +11,7 @@
:javascript :javascript
$(function(){ $(function(){
var w, h; $('.files .file').each(function(){
$('.diff_file').each(function(){ new CommitFile(this);
$('.image.diff_removed img', this).on('load', $.proxy(function(event){
var w = event.currentTarget.naturalWidth
, h = event.currentTarget.naturalHeight;
$('.image.diff_removed .image-info', this).append(' | <b>W:</b> ' + w + 'px | <b>H:</b> ' + h + 'px');
}, this));
$('.image.diff_added img', this).on('load', $.proxy(function(event){
var w = event.currentTarget.naturalWidth
, h = event.currentTarget.naturalHeight;
$('.image.diff_added .image-info', this).append(' | <b>W:</b> ' + w + 'px | <b>H:</b> ' + h + 'px');
}, this));
}); });
}); });
...@@ -2,5 +2,5 @@ ...@@ -2,5 +2,5 @@
%div.ui-box %div.ui-box
%h5.title %h5.title
%i.icon-calendar %i.icon-calendar
= day.stamp("28 Aug, 2010") %span= day.stamp("28 Aug, 2010")
%ul.well-list= render commits %ul.well-list= render commits
...@@ -12,19 +12,20 @@ ...@@ -12,19 +12,20 @@
.file-stats .file-stats
= render "commits/diff_head", diffs: diffs = render "commits/diff_head", diffs: diffs
- unless @suppress_diff .files
- unless @suppress_diff
- diffs.each_with_index do |diff, i| - diffs.each_with_index do |diff, i|
- next if diff.diff.empty? - next if diff.diff.empty?
- file = (@commit.tree / diff.new_path) - file = (@commit.tree / diff.new_path)
- file = (@commit.prev_commit.tree / diff.old_path) unless file - file = (@commit.prev_commit.tree / diff.old_path) unless file
- next unless file - next unless file
.diff_file{id: "diff-#{i}"} .file{id: "diff-#{i}"}
.diff_file_header .header
- if diff.deleted_file - if diff.deleted_file
%span= diff.old_path %span= diff.old_path
- if @commit.prev_commit - if @commit.prev_commit
= link_to project_tree_path(@project, tree_join(@commit.prev_commit_id, diff.new_path)), {:class => 'btn right view-commit'} do = link_to project_tree_path(@project, tree_join(@commit.prev_commit_id, diff.new_path)), {:class => 'btn right view-file'} do
View file @ View file @
%span.commit-short-id= @commit.short_id(6) %span.commit-short-id= @commit.short_id(6)
- else - else
...@@ -32,30 +33,17 @@ ...@@ -32,30 +33,17 @@
- if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode - if diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
%span.file-mode= "#{diff.a_mode}#{diff.b_mode}" %span.file-mode= "#{diff.a_mode}#{diff.b_mode}"
= link_to project_tree_path(@project, tree_join(@commit.id, diff.new_path)), {:class => 'btn very_small right view-commit'} do = link_to project_tree_path(@project, tree_join(@commit.id, diff.new_path)), {:class => 'btn very_small right view-file'} do
View file @ View file @
%span.commit-short-id= @commit.short_id(6) %span.commit-short-id= @commit.short_id(6)
%br/ .content
.diff_file_content -# Skipp all non non-supported blobs
-# Skip all non-supported blobs
- next unless file.respond_to?('text?') - next unless file.respond_to?('text?')
- if file.text? - if file.text?
= render "commits/text_diff", diff: diff, index: i = render "commits/text_file", diff: diff, index: i
- elsif file.image? - elsif file.image?
- old_file = (@commit.prev_commit.tree / diff.old_path) if !@commit.prev_commit.nil? - old_file = (@commit.prev_commit.tree / diff.old_path) if !@commit.prev_commit.nil?
- if diff.renamed_file || diff.new_file || diff.deleted_file = render "commits/image", diff: diff, old_file: old_file, file: file, index: i
.diff_file_content_image
.image{class: image_diff_class(diff)}
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
%div.image-info= "#{number_to_human_size file.size}"
- else
.diff_file_content_image.img_compared
.image.diff_removed
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
%div.image-info= "#{number_to_human_size file.size}"
.image.diff_added
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
%div.image-info= "#{number_to_human_size file.size}"
- else - else
%p.nothing_here_message No preview for this file type %p.nothing_here_message No preview for this file type
- if diff.renamed_file || diff.new_file || diff.deleted_file
.image
%span.wrap
.frame{class: image_diff_class(diff)}
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
%p.image-info= "#{number_to_human_size file.size}"
- else
.image
%div.two-up.view
%span.wrap
.frame.deleted
%a{href: project_tree_path(@project, tree_join(@commit.id, diff.old_path))}
%img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
%p.image-info.hide
%span.meta-filesize= "#{number_to_human_size old_file.size}"
|
%b W:
%span.meta-width
|
%b H:
%span.meta-height
%span.wrap
.frame.added
%a{href: project_tree_path(@project, tree_join(@commit.id, diff.new_path))}
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
%p.image-info.hide
%span.meta-filesize= "#{number_to_human_size file.size}"
|
%b W:
%span.meta-width
|
%b H:
%span.meta-height
%div.swipe.view.hide
.swipe-frame
.frame.deleted
%img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
.swipe-wrap
.frame.added
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
%span.swipe-bar
%span.top-handle
%span.bottom-handle
%div.onion-skin.view.hide
.onion-skin-frame
.frame.deleted
%img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"}
.frame.added
%img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"}
.controls
.transparent
.drag-track
.dragger{:style => "left: 0px;"}
.opaque
.view-modes.hide
%ul.view-modes-menu
%li.two-up{data: {mode: 'two-up'}} 2-up
%li.swipe{data: {mode: 'swipe'}} Swipe
%li.onion-skin{data: {mode: 'onion-skin'}} Onion skin
\ No newline at end of file
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- if too_big - if too_big
%a.supp_diff_link Diff suppressed. Click to show %a.supp_diff_link Diff suppressed. Click to show
%table{class: "#{'hide' if too_big}"} %table.text-file{class: "#{'hide' if too_big}"}
- each_diff_line(diff, index) do |line, type, line_code, line_new, line_old| - each_diff_line(diff, index) do |line, type, line_code, line_new, line_old|
%tr.line_holder{ id: line_code } %tr.line_holder{ id: line_code }
- if type == "match" - if type == "match"
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
= breadcrumbs = breadcrumbs
%div{id: dom_id(@project)} %div{id: dom_id(@project)}
#commits_list= render "commits" #commits-list= render "commits"
.clear .clear
.loading{ style: "display:none;"} .loading{ style: "display:none;"}
......
.groups_box .ui-box
%h5.title %h5.title
Groups Groups
%small %small
(#{groups.count}) (#{groups.count})
- if current_user.can_create_group? - if current_user.can_create_group?
%span.right %span.right
= link_to new_admin_group_path, class: "btn very_small info" do = link_to new_group_path, class: "btn very_small info" do
%i.icon-plus %i.icon-plus
New Group New Group
%ul.well-list %ul.well-list
...@@ -13,8 +13,6 @@ ...@@ -13,8 +13,6 @@
%li %li
= link_to group_path(id: group.path), class: dom_class(group) do = link_to group_path(id: group.path), class: dom_class(group) do
%strong.well-title= truncate(group.name, length: 35) %strong.well-title= truncate(group.name, length: 35)
%span.arrow %span.right.light
&rarr; - if group.owner == current_user
%span.last_activity %i.icon-wrench
%strong Projects:
%span= current_user.authorized_projects.where(namespace_id: group.id).count
...@@ -2,19 +2,12 @@ ...@@ -2,19 +2,12 @@
%h5.title %h5.title
Projects Projects
%small %small
(#{projects.total_count}) (#{@projects_count})
- if current_user.can_create_project? - if current_user.can_create_project?
%span.right %span.right
= link_to new_project_path, class: "btn very_small info" do = link_to new_project_path, class: "btn very_small info" do
%i.icon-plus %i.icon-plus
New Project New Project
%ul.nav.nav-projects-tabs
= nav_tab :scope, nil do
= link_to "All", dashboard_path
= nav_tab :scope, 'personal' do
= link_to "Personal", dashboard_path(scope: 'personal')
= nav_tab :scope, 'joined' do
= link_to "Joined", dashboard_path(scope: 'joined')
%ul.well-list %ul.well-list
- projects.each do |project| - projects.each do |project|
...@@ -33,4 +26,6 @@ ...@@ -33,4 +26,6 @@
- if projects.blank? - if projects.blank?
%li %li
%h3.nothing_here_message There are no projects here. %h3.nothing_here_message There are no projects here.
.bottom= paginate projects, theme: "gitlab" - if @projects_count > 20
%li.bottom
%strong= link_to "show all projects", projects_dashboard_path
- if @teams.present?
= render "teams", teams: @teams
- if @groups.present? - if @groups.present?
= render "groups", groups: @groups = render "groups", groups: @groups
= render "projects", projects: @projects = render "projects", projects: @projects
......
.ui-box.teams-box
%h5.title
Teams
%small
(#{@teams.count})
%span.right
= link_to new_team_path, class: "btn very_small info" do
%i.icon-plus
New Team
%ul.well-list
- @teams.each do |team|
%li
= link_to team_path(id: team.path), class: dom_class(team) do
%strong.well-title= truncate(team.name, length: 35)
%span.right.light
- if team.owner == current_user
%i.icon-wrench
- tm = current_user.user_team_user_relationships.find_by_user_team_id(team.id)
- if tm
= tm.access_human
xml.instruct! xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "#{current_user.name} issues" xml.title "#{current_user.name} issues"
xml.link :href => dashboard_issues_url(:atom, :private_token => current_user.private_token), :rel => "self", :type => "application/atom+xml" xml.link :href => issues_dashboard_url(:atom, :private_token => current_user.private_token), :rel => "self", :type => "application/atom+xml"
xml.link :href => dashboard_issues_url(:private_token => current_user.private_token), :rel => "alternate", :type => "text/html" xml.link :href => issues_dashboard_url(:private_token => current_user.private_token), :rel => "alternate", :type => "text/html"
xml.id dashboard_issues_url(:private_token => current_user.private_token) xml.id issues_dashboard_url(:private_token => current_user.private_token)
xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
@issues.each do |issue| @issues.each do |issue|
......
%h3.page_title
Projects
%span
(#{@projects.total_count})
- if current_user.can_create_project?
%span.right
= link_to new_project_path, class: "btn very_small info" do
%i.icon-plus
New Project
%hr
.row
.span3
%ul.nav.nav-pills.nav-stacked
= nav_tab :scope, nil do
= link_to "All", projects_dashboard_path
= nav_tab :scope, 'personal' do
= link_to "Personal", projects_dashboard_path(scope: 'personal')
= nav_tab :scope, 'joined' do
= link_to "Joined", projects_dashboard_path(scope: 'joined')
.span9
= form_tag projects_dashboard_path, method: 'get' do
%fieldset.dashboard-search-filter
= hidden_field_tag "scope", params[:scope]
= search_field_tag "search", params[:search], { placeholder: 'Search', class: 'left input-xxlarge' }
= button_tag type: 'submit', class: 'btn' do
%i.icon-search
%ul.well-list
- @projects.each do |project|
%li.clearfix
.left
= link_to project_path(project), class: dom_class(project) do
- if project.namespace
= project.namespace.human_name
\/
%strong.well-title
= truncate(project.name, length: 25)
%br
%small.light
%strong Last activity:
%span= project_last_activity(project)
.right.light
- if project.owner == current_user
%i.icon-wrench
- tm = project.team.get_tm(current_user.id)
- if tm
= tm.project_access_human
- if @projects.blank?
%li
%h3.nothing_here_message There are no projects here.
.bottom= paginate @projects, theme: "gitlab"
xml.instruct! xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
xml.title "#{@user.name} issues" xml.title "#{@user.name} issues"
xml.link :href => dashboard_issues_url(:atom, :private_token => @user.private_token), :rel => "self", :type => "application/atom+xml" xml.link :href => issues_dashboard_url(:atom, :private_token => @user.private_token), :rel => "self", :type => "application/atom+xml"
xml.link :href => dashboard_issues_url(:private_token => @user.private_token), :rel => "alternate", :type => "text/html" xml.link :href => issues_dashboard_url(:private_token => @user.private_token), :rel => "alternate", :type => "text/html"
xml.id dashboard_issues_url(:private_token => @user.private_token) xml.id issues_dashboard_url(:private_token => @user.private_token)
xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
@issues.each do |issue| @issues.each do |issue|
......
%h3.page_title New Group
%hr
= form_for @group do |f|
- if @group.errors.any?
.alert-message.block-message.error
%span= @group.errors.full_messages.first
.clearfix
= f.label :name do
Group name is
.input
= f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left"
&nbsp;
= f.submit 'Create group', class: "btn primary"
%hr
.padded
%ul
%li Group is kind of directory for several projects
%li All created groups are private
%li People within a group see only projects they have access to
%li All projects of group will be stored in group directory
%li You will be able to move existing projects into group
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment