Collaborators have write access as default, and can be changed via repository collaboration settings page to change between read, write and admin.tags/v0.9.99
bindIgnErr := binding.BindIgnErr | bindIgnErr := binding.BindIgnErr | ||||
// FIXME: not all routes need go through same middlewares. | |||||
// Especially some AJAX requests, we can reduce middleware number to improve performance. | |||||
// Routers. | // Routers. | ||||
m.Get("/", ignSignIn, routers.Home) | m.Get("/", ignSignIn, routers.Home) | ||||
m.Get("/explore", ignSignIn, routers.Explore) | m.Get("/explore", ignSignIn, routers.Explore) | ||||
m.Group("/settings", func() { | m.Group("/settings", func() { | ||||
m.Combo("").Get(repo.Settings). | m.Combo("").Get(repo.Settings). | ||||
Post(bindIgnErr(auth.RepoSettingForm{}), repo.SettingsPost) | Post(bindIgnErr(auth.RepoSettingForm{}), repo.SettingsPost) | ||||
m.Combo("/collaboration").Get(repo.Collaboration).Post(repo.CollaborationPost) | |||||
m.Group("/collaboration", func() { | |||||
m.Combo("").Get(repo.Collaboration).Post(repo.CollaborationPost) | |||||
m.Post("/access_mode", repo.ChangeCollaborationAccessMode) | |||||
m.Post("/delete", repo.DeleteCollaboration) | |||||
}) | |||||
m.Group("/hooks", func() { | m.Group("/hooks", func() { | ||||
m.Get("", repo.Webhooks) | m.Get("", repo.Webhooks) |
still_has_org = Your account still has membership in at least one organization, you have to leave or delete your memberships first. | still_has_org = Your account still has membership in at least one organization, you have to leave or delete your memberships first. | ||||
org_still_own_repo = This organization still has ownership of repositories, you must delete or transfer them first. | org_still_own_repo = This organization still has ownership of repositories, you must delete or transfer them first. | ||||
still_own_user = This authentication is still in use by at least one user, please remove them from the authentication and try again. | |||||
target_branch_not_exist = Target branch does not exist. | target_branch_not_exist = Target branch does not exist. | ||||
[user] | [user] | ||||
settings.confirm_delete = Confirm Deletion | settings.confirm_delete = Confirm Deletion | ||||
settings.add_collaborator = Add New Collaborator | settings.add_collaborator = Add New Collaborator | ||||
settings.add_collaborator_success = New collaborator has been added. | settings.add_collaborator_success = New collaborator has been added. | ||||
settings.delete_collaborator = Delete | |||||
settings.collaborator_deletion = Collaborator Deletion | |||||
settings.collaborator_deletion_desc = This user will no longer have collaboration access to this repository after deletion. Do you want to continue? | |||||
settings.remove_collaborator_success = Collaborator has been removed. | settings.remove_collaborator_success = Collaborator has been removed. | ||||
settings.search_user_placeholder = Search user... | settings.search_user_placeholder = Search user... | ||||
settings.org_not_allowed_to_be_collaborator = Organization is not allowed to be added as a collaborator. | settings.org_not_allowed_to_be_collaborator = Organization is not allowed to be added as a collaborator. | ||||
auths.delete = Delete This Authentication | auths.delete = Delete This Authentication | ||||
auths.delete_auth_title = Authentication Deletion | auths.delete_auth_title = Authentication Deletion | ||||
auths.delete_auth_desc = This authentication is going to be deleted, do you want to continue? | auths.delete_auth_desc = This authentication is going to be deleted, do you want to continue? | ||||
auths.still_in_used = This authentication is still used by some users, please delete or convert these users to another login type first. | |||||
auths.deletion_success = Authentication has been deleted successfully! | auths.deletion_success = Authentication has been deleted successfully! | ||||
config.server_config = Server Configuration | config.server_config = Server Configuration |
type AccessMode int | type AccessMode int | ||||
const ( | const ( | ||||
ACCESS_MODE_NONE AccessMode = iota | |||||
ACCESS_MODE_READ | |||||
ACCESS_MODE_WRITE | |||||
ACCESS_MODE_ADMIN | |||||
ACCESS_MODE_OWNER | |||||
ACCESS_MODE_NONE AccessMode = iota // 0 | |||||
ACCESS_MODE_READ // 1 | |||||
ACCESS_MODE_WRITE // 2 | |||||
ACCESS_MODE_ADMIN // 3 | |||||
ACCESS_MODE_OWNER // 4 | |||||
) | ) | ||||
// Access represents the highest access level of a user to the repository. The only access type | // Access represents the highest access level of a user to the repository. The only access type | ||||
return nil | return nil | ||||
} | } | ||||
// FIXME: should be able to have read-only access. | |||||
// Give all collaborators write access. | |||||
// refreshCollaboratorAccesses retrieves repository collaborations with their access modes. | |||||
func (repo *Repository) refreshCollaboratorAccesses(e Engine, accessMap map[int64]AccessMode) error { | func (repo *Repository) refreshCollaboratorAccesses(e Engine, accessMap map[int64]AccessMode) error { | ||||
collaborators, err := repo.getCollaborators(e) | |||||
collaborations, err := repo.getCollaborations(e) | |||||
if err != nil { | if err != nil { | ||||
return fmt.Errorf("getCollaborators: %v", err) | |||||
return fmt.Errorf("getCollaborations: %v", err) | |||||
} | } | ||||
for _, c := range collaborators { | |||||
accessMap[c.Id] = ACCESS_MODE_WRITE | |||||
for _, c := range collaborations { | |||||
accessMap[c.UserID] = c.Mode | |||||
} | } | ||||
return nil | return nil | ||||
} | } |
return nil, err | return nil, err | ||||
} | } | ||||
// Compose comment action, could be plain comment, close or reopen issue. | |||||
// Compose comment action, could be plain comment, close or reopen issue/pull request. | |||||
// This object will be used to notify watchers in the end of function. | // This object will be used to notify watchers in the end of function. | ||||
act := &Action{ | act := &Action{ | ||||
ActUserID: opts.Doer.Id, | ActUserID: opts.Doer.Id, | ||||
if err != nil { | if err != nil { | ||||
return nil, err | return nil, err | ||||
} | } | ||||
case COMMENT_TYPE_CLOSE: | case COMMENT_TYPE_CLOSE: | ||||
act.OpType = ACTION_CLOSE_ISSUE | act.OpType = ACTION_CLOSE_ISSUE | ||||
if opts.Issue.IsPull { | if opts.Issue.IsPull { |
return "/" + repo.MustOwner().Name + "/" + repo.Name | return "/" + repo.MustOwner().Name + "/" + repo.Name | ||||
} | } | ||||
func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) string { | func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) string { | ||||
return fmt.Sprintf("%s/%s/compare/%s...%s", repo.MustOwner().Name, repo.Name, oldCommitID, newCommitID) | return fmt.Sprintf("%s/%s/compare/%s...%s", repo.MustOwner().Name, repo.Name, oldCommitID, newCommitID) | ||||
} | } | ||||
// ***** END: Repository.NumForks ***** | // ***** END: Repository.NumForks ***** | ||||
} | } | ||||
// _________ .__ .__ ___. __ .__ | |||||
// \_ ___ \ ____ | | | | _____ \_ |__ ________________ _/ |_|__| ____ ____ | |||||
// / \ \/ / _ \| | | | \__ \ | __ \ / _ \_ __ \__ \\ __\ |/ _ \ / \ | |||||
// \ \___( <_> ) |_| |__/ __ \| \_\ ( <_> ) | \// __ \| | | ( <_> ) | \ | |||||
// \______ /\____/|____/____(____ /___ /\____/|__| (____ /__| |__|\____/|___| / | |||||
// \/ \/ \/ \/ \/ | |||||
// A Collaboration is a relation between an individual and a repository | |||||
type Collaboration struct { | |||||
ID int64 `xorm:"pk autoincr"` | |||||
RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` | |||||
UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` | |||||
Created time.Time `xorm:"CREATED"` | |||||
} | |||||
// Add collaborator and accompanying access | |||||
func (repo *Repository) AddCollaborator(u *User) error { | |||||
collaboration := &Collaboration{ | |||||
RepoID: repo.ID, | |||||
UserID: u.Id, | |||||
} | |||||
has, err := x.Get(collaboration) | |||||
if err != nil { | |||||
return err | |||||
} else if has { | |||||
return nil | |||||
} | |||||
if err = repo.GetOwner(); err != nil { | |||||
return fmt.Errorf("GetOwner: %v", err) | |||||
} | |||||
sess := x.NewSession() | |||||
defer sessionRelease(sess) | |||||
if err = sess.Begin(); err != nil { | |||||
return err | |||||
} | |||||
if _, err = sess.InsertOne(collaboration); err != nil { | |||||
return err | |||||
} | |||||
if repo.Owner.IsOrganization() { | |||||
err = repo.recalculateTeamAccesses(sess, 0) | |||||
} else { | |||||
err = repo.recalculateAccesses(sess) | |||||
} | |||||
if err != nil { | |||||
return fmt.Errorf("recalculateAccesses 'team=%v': %v", repo.Owner.IsOrganization(), err) | |||||
} | |||||
return sess.Commit() | |||||
} | |||||
func (repo *Repository) getCollaborators(e Engine) ([]*User, error) { | |||||
collaborations := make([]*Collaboration, 0) | |||||
if err := e.Find(&collaborations, &Collaboration{RepoID: repo.ID}); err != nil { | |||||
return nil, err | |||||
} | |||||
users := make([]*User, len(collaborations)) | |||||
for i, c := range collaborations { | |||||
user, err := getUserByID(e, c.UserID) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
users[i] = user | |||||
} | |||||
return users, nil | |||||
} | |||||
// GetCollaborators returns the collaborators for a repository | |||||
func (repo *Repository) GetCollaborators() ([]*User, error) { | |||||
return repo.getCollaborators(x) | |||||
} | |||||
// Delete collaborator and accompanying access | |||||
func (repo *Repository) DeleteCollaborator(u *User) (err error) { | |||||
collaboration := &Collaboration{ | |||||
RepoID: repo.ID, | |||||
UserID: u.Id, | |||||
} | |||||
sess := x.NewSession() | |||||
defer sessionRelease(sess) | |||||
if err = sess.Begin(); err != nil { | |||||
return err | |||||
} | |||||
if has, err := sess.Delete(collaboration); err != nil || has == 0 { | |||||
return err | |||||
} else if err = repo.recalculateAccesses(sess); err != nil { | |||||
return err | |||||
} | |||||
return sess.Commit() | |||||
} | |||||
// __ __ __ .__ | // __ __ __ .__ | ||||
// / \ / \_____ _/ |_ ____ | |__ | // / \ / \_____ _/ |_ ____ | |__ | ||||
// \ \/\/ /\__ \\ __\/ ___\| | \ | // \ \/\/ /\__ \\ __\/ ___\| | \ |
// Copyright 2016 The Gogs Authors. All rights reserved. | |||||
// Use of this source code is governed by a MIT-style | |||||
// license that can be found in the LICENSE file. | |||||
package models | |||||
import ( | |||||
"fmt" | |||||
"time" | |||||
) | |||||
// Collaboration represent the relation between an individual and a repository. | |||||
type Collaboration struct { | |||||
ID int64 `xorm:"pk autoincr"` | |||||
RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` | |||||
UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` | |||||
Mode AccessMode `xorm:"DEFAULT 2 NOT NULL"` | |||||
Created time.Time `xorm:"CREATED"` | |||||
} | |||||
func (c *Collaboration) ModeName() string { | |||||
switch c.Mode { | |||||
case ACCESS_MODE_READ: | |||||
return "Read" | |||||
case ACCESS_MODE_WRITE: | |||||
return "Write" | |||||
case ACCESS_MODE_ADMIN: | |||||
return "Admin" | |||||
} | |||||
return "Undefined" | |||||
} | |||||
// AddCollaborator adds new collaboration relation between an individual and a repository. | |||||
func (repo *Repository) AddCollaborator(u *User) error { | |||||
collaboration := &Collaboration{ | |||||
RepoID: repo.ID, | |||||
UserID: u.Id, | |||||
} | |||||
has, err := x.Get(collaboration) | |||||
if err != nil { | |||||
return err | |||||
} else if has { | |||||
return nil | |||||
} | |||||
collaboration.Mode = ACCESS_MODE_WRITE | |||||
sess := x.NewSession() | |||||
defer sessionRelease(sess) | |||||
if err = sess.Begin(); err != nil { | |||||
return err | |||||
} | |||||
if _, err = sess.InsertOne(collaboration); err != nil { | |||||
return err | |||||
} | |||||
if repo.Owner.IsOrganization() { | |||||
err = repo.recalculateTeamAccesses(sess, 0) | |||||
} else { | |||||
err = repo.recalculateAccesses(sess) | |||||
} | |||||
if err != nil { | |||||
return fmt.Errorf("recalculateAccesses 'team=%v': %v", repo.Owner.IsOrganization(), err) | |||||
} | |||||
return sess.Commit() | |||||
} | |||||
func (repo *Repository) getCollaborations(e Engine) ([]*Collaboration, error) { | |||||
collaborations := make([]*Collaboration, 0) | |||||
return collaborations, e.Find(&collaborations, &Collaboration{RepoID: repo.ID}) | |||||
} | |||||
// Collaborator represents a user with collaboration details. | |||||
type Collaborator struct { | |||||
*User | |||||
Collaboration *Collaboration | |||||
} | |||||
func (repo *Repository) getCollaborators(e Engine) ([]*Collaborator, error) { | |||||
collaborations, err := repo.getCollaborations(e) | |||||
if err != nil { | |||||
return nil, fmt.Errorf("getCollaborations: %v", err) | |||||
} | |||||
collaborators := make([]*Collaborator, len(collaborations)) | |||||
for i, c := range collaborations { | |||||
user, err := getUserByID(e, c.UserID) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
collaborators[i] = &Collaborator{ | |||||
User: user, | |||||
Collaboration: c, | |||||
} | |||||
} | |||||
return collaborators, nil | |||||
} | |||||
// GetCollaborators returns the collaborators for a repository | |||||
func (repo *Repository) GetCollaborators() ([]*Collaborator, error) { | |||||
return repo.getCollaborators(x) | |||||
} | |||||
// ChangeCollaborationAccessMode sets new access mode for the collaboration. | |||||
func (repo *Repository) ChangeCollaborationAccessMode(uid int64, mode AccessMode) error { | |||||
// Discard invalid input | |||||
if mode <= ACCESS_MODE_NONE || mode > ACCESS_MODE_OWNER { | |||||
return nil | |||||
} | |||||
collaboration := &Collaboration{ | |||||
RepoID: repo.ID, | |||||
UserID: uid, | |||||
} | |||||
has, err := x.Get(collaboration) | |||||
if err != nil { | |||||
return fmt.Errorf("get collaboration: %v", err) | |||||
} else if !has { | |||||
return nil | |||||
} | |||||
collaboration.Mode = mode | |||||
sess := x.NewSession() | |||||
defer sessionRelease(sess) | |||||
if err = sess.Begin(); err != nil { | |||||
return err | |||||
} | |||||
if _, err = sess.Id(collaboration.ID).AllCols().Update(collaboration); err != nil { | |||||
return fmt.Errorf("update collaboration: %v", err) | |||||
} else if _, err = sess.Exec("UPDATE access SET mode = ? WHERE user_id = ? AND repo_id = ?", mode, uid, repo.ID); err != nil { | |||||
return fmt.Errorf("update access table: %v", err) | |||||
} | |||||
return sess.Commit() | |||||
} | |||||
// DeleteCollaboration removes collaboration relation between the user and repository. | |||||
func (repo *Repository) DeleteCollaboration(uid int64) (err error) { | |||||
collaboration := &Collaboration{ | |||||
RepoID: repo.ID, | |||||
UserID: uid, | |||||
} | |||||
sess := x.NewSession() | |||||
defer sessionRelease(sess) | |||||
if err = sess.Begin(); err != nil { | |||||
return err | |||||
} | |||||
if has, err := sess.Delete(collaboration); err != nil || has == 0 { | |||||
return err | |||||
} else if err = repo.recalculateAccesses(sess); err != nil { | |||||
return err | |||||
} | |||||
return sess.Commit() | |||||
} |
"outputPathIsOutsideProject": 0, | "outputPathIsOutsideProject": 0, | ||||
"outputPathIsSetByUser": 0 | "outputPathIsSetByUser": 0 | ||||
}, | }, | ||||
"\/css\/gogs.min.css": { | |||||
"fileType": 16, | |||||
"ignore": 1, | |||||
"ignoreWasSetByUser": 0, | |||||
"inputAbbreviatedPath": "\/css\/gogs.min.css", | |||||
"outputAbbreviatedPath": "No Output Path", | |||||
"outputPathIsOutsideProject": 0, | |||||
"outputPathIsSetByUser": 0 | |||||
}, | |||||
"\/css\/semantic-2.1.8.min.css": { | "\/css\/semantic-2.1.8.min.css": { | ||||
"fileType": 16, | "fileType": 16, | ||||
"ignore": 0, | "ignore": 0, |
background-size: contain; | background-size: contain; | ||||
} | } | ||||
body { | body { | ||||
font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif, '微软雅黑'; | |||||
font-family: "Helvetica Neue", "Microsoft YaHei", Arial, Helvetica, sans-serif !important; | |||||
background-color: #fff; | background-color: #fff; | ||||
overflow-y: scroll; | overflow-y: scroll; | ||||
} | } | ||||
.ui.container.fluid.padded { | .ui.container.fluid.padded { | ||||
padding: 0 10px 0 10px; | padding: 0 10px 0 10px; | ||||
} | } | ||||
.ui.form .ui.button { | |||||
font-weight: normal; | |||||
} | |||||
.ui .text.red { | .ui .text.red { | ||||
color: #d95c5c !important; | color: #d95c5c !important; | ||||
} | } | ||||
.ui.status.buttons .octicon { | .ui.status.buttons .octicon { | ||||
margin-right: 4px; | margin-right: 4px; | ||||
} | } | ||||
.ui.inline.delete-button { | |||||
padding: 8px 15px; | |||||
font-weight: normal; | |||||
} | |||||
.overflow.menu .items { | .overflow.menu .items { | ||||
max-height: 300px; | max-height: 300px; | ||||
overflow-y: auto; | overflow-y: auto; | ||||
.repository.settings.collaboration .collaborator.list { | .repository.settings.collaboration .collaborator.list { | ||||
padding: 0; | padding: 0; | ||||
} | } | ||||
.repository.settings.collaboration .collaborator.list .item { | |||||
padding: 10px 20px; | |||||
.repository.settings.collaboration .collaborator.list > .item { | |||||
margin: 0; | |||||
line-height: 2em; | |||||
} | } | ||||
.repository.settings.collaboration .collaborator.list .item:not(:last-child) { | |||||
.repository.settings.collaboration .collaborator.list > .item:not(:last-child) { | |||||
border-bottom: 1px solid #DDD; | border-bottom: 1px solid #DDD; | ||||
} | } | ||||
.repository.settings.collaboration #repo-collab-form #search-user-box .results { | .repository.settings.collaboration #repo-collab-form #search-user-box .results { |
} | } | ||||
} | } | ||||
function initRepositoryCollaboration(){ | |||||
console.log('initRepositoryCollaboration'); | |||||
// Change collaborator access mode | |||||
$('.access-mode.menu .item').click(function(){ | |||||
var $menu = $(this).parent(); | |||||
$.post($menu.data('url'), { | |||||
"_csrf": csrf, | |||||
"uid": $menu.data('uid'), | |||||
"mode": $(this).data('value') | |||||
}) | |||||
}); | |||||
} | |||||
function initWiki() { | function initWiki() { | ||||
if ($('.repository.wiki').length == 0) { | if ($('.repository.wiki').length == 0) { | ||||
return; | return; | ||||
initAdmin(); | initAdmin(); | ||||
var routes = { | var routes = { | ||||
'div.user.settings': initUserSettings | |||||
'div.user.settings': initUserSettings, | |||||
'div.repository.settings.collaboration': initRepositoryCollaboration | |||||
}; | }; | ||||
var selector; | var selector; |
@footer-margin: 40px; | @footer-margin: 40px; | ||||
body { | body { | ||||
font-family: 'Helvetica Neue',Arial,Helvetica,sans-serif,'微软雅黑'; | |||||
font-family: "Helvetica Neue", "Microsoft YaHei", Arial, Helvetica, sans-serif !important; | |||||
background-color: #fff; | background-color: #fff; | ||||
overflow-y: scroll; | overflow-y: scroll; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
&.form { | |||||
.ui.button { | |||||
font-weight: normal; | |||||
} | |||||
} | |||||
.text { | .text { | ||||
&.red { | &.red { | ||||
color: #d95c5c !important; | color: #d95c5c !important; | ||||
margin-right: 4px; | margin-right: 4px; | ||||
} | } | ||||
} | } | ||||
&.inline.delete-button { | |||||
padding: 8px 15px; | |||||
font-weight: normal; | |||||
} | |||||
} | } | ||||
.overflow.menu { | .overflow.menu { |
.collaborator.list { | .collaborator.list { | ||||
padding: 0; | padding: 0; | ||||
.item { | |||||
padding: 10px 20px; | |||||
>.item { | |||||
margin: 0; | |||||
line-height: 2em; | |||||
&:not(:last-child) { | &:not(:last-child) { | ||||
border-bottom: 1px solid #DDD; | border-bottom: 1px solid #DDD; |
package admin | package admin | ||||
import ( | import ( | ||||
"fmt" | |||||
"github.com/Unknwon/com" | "github.com/Unknwon/com" | ||||
"github.com/go-xorm/core" | "github.com/go-xorm/core" | ||||
if err = models.DeleteSource(source); err != nil { | if err = models.DeleteSource(source); err != nil { | ||||
switch err { | switch err { | ||||
case models.ErrAuthenticationUserUsed: | case models.ErrAuthenticationUserUsed: | ||||
ctx.Flash.Error("form.still_own_user") | |||||
ctx.Redirect(setting.AppSubUrl + "/admin/auths/" + ctx.Params(":authid")) | |||||
ctx.Flash.Error(ctx.Tr("admin.auths.still_in_used")) | |||||
default: | default: | ||||
ctx.Handle(500, "DeleteSource", err) | |||||
ctx.Flash.Error(fmt.Sprintf("DeleteSource: %v", err)) | |||||
} | } | ||||
ctx.JSON(200, map[string]interface{}{ | |||||
"redirect": setting.AppSubUrl + "/admin/auths/" + ctx.Params(":authid"), | |||||
}) | |||||
return | return | ||||
} | } | ||||
log.Trace("Authentication deleted by admin(%s): %d", ctx.User.Name, source.ID) | log.Trace("Authentication deleted by admin(%s): %d", ctx.User.Name, source.ID) |
ctx.Data["Title"] = ctx.Tr("repo.settings") | ctx.Data["Title"] = ctx.Tr("repo.settings") | ||||
ctx.Data["PageIsSettingsCollaboration"] = true | ctx.Data["PageIsSettingsCollaboration"] = true | ||||
// Delete collaborator. | |||||
remove := strings.ToLower(ctx.Query("remove")) | |||||
if len(remove) > 0 && remove != ctx.Repo.Owner.LowerName { | |||||
u, err := models.GetUserByName(remove) | |||||
if err != nil { | |||||
ctx.Handle(500, "GetUserByName", err) | |||||
return | |||||
} | |||||
if err := ctx.Repo.Repository.DeleteCollaborator(u); err != nil { | |||||
ctx.Handle(500, "DeleteCollaborator", err) | |||||
return | |||||
} | |||||
ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success")) | |||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") | |||||
return | |||||
} | |||||
users, err := ctx.Repo.Repository.GetCollaborators() | users, err := ctx.Repo.Repository.GetCollaborators() | ||||
if err != nil { | if err != nil { | ||||
ctx.Handle(500, "GetCollaborators", err) | ctx.Handle(500, "GetCollaborators", err) | ||||
return | return | ||||
} | } | ||||
ctx.Data["Collaborators"] = users | ctx.Data["Collaborators"] = users | ||||
ctx.HTML(200, COLLABORATION) | ctx.HTML(200, COLLABORATION) | ||||
} | } | ||||
ctx.Redirect(setting.AppSubUrl + ctx.Req.URL.Path) | ctx.Redirect(setting.AppSubUrl + ctx.Req.URL.Path) | ||||
} | } | ||||
func ChangeCollaborationAccessMode(ctx *middleware.Context) { | |||||
if err := ctx.Repo.Repository.ChangeCollaborationAccessMode( | |||||
ctx.QueryInt64("uid"), | |||||
models.AccessMode(ctx.QueryInt("mode"))); err != nil { | |||||
log.Error(4, "ChangeCollaborationAccessMode: %v", err) | |||||
} | |||||
} | |||||
func DeleteCollaboration(ctx *middleware.Context) { | |||||
if err := ctx.Repo.Repository.DeleteCollaboration(ctx.QueryInt64("id")); err != nil { | |||||
ctx.Flash.Error("DeleteCollaboration: " + err.Error()) | |||||
} else { | |||||
ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success")) | |||||
} | |||||
ctx.JSON(200, map[string]interface{}{ | |||||
"redirect": ctx.Repo.RepoLink + "/settings/collaboration", | |||||
}) | |||||
} | |||||
func parseOwnerAndRepo(ctx *middleware.Context) (*models.User, *models.Repository) { | func parseOwnerAndRepo(ctx *middleware.Context) (*models.User, *models.Repository) { | ||||
owner, err := models.GetUserByName(ctx.Params(":username")) | owner, err := models.GetUserByName(ctx.Params(":username")) | ||||
if err != nil { | if err != nil { |
</h4> | </h4> | ||||
<div class="ui attached segment collaborator list"> | <div class="ui attached segment collaborator list"> | ||||
{{range .Collaborators}} | {{range .Collaborators}} | ||||
<div class="item"> | |||||
{{if not (eq .Id $.Owner.Id)}} | |||||
<a href="{{$.RepoLink}}/settings/collaboration?remove={{.Name}}" class="ui right text red"><i class="fa fa-times"></i></a> | |||||
{{end}} | |||||
<a href="{{AppSubUrl}}/{{.Name}}"> | |||||
<img class="ui avatar image" src="{{.AvatarLink}}"> | |||||
{{.DisplayName}} | |||||
</a> | |||||
<div class="item ui grid"> | |||||
<div class="ui five wide column"> | |||||
<a href="{{AppSubUrl}}/{{.Name}}"> | |||||
<img class="ui avatar image" src="{{.AvatarLink}}"> | |||||
{{.DisplayName}} | |||||
</a> | |||||
</div> | |||||
<div class="ui eight wide column"> | |||||
<span class="octicon octicon-shield"></span> | |||||
<div class="ui inline dropdown"> | |||||
<div class="text">{{.Collaboration.ModeName}}</div> | |||||
<i class="dropdown icon"></i> | |||||
<div class="access-mode menu" data-url="{{$.Link}}/access_mode" data-uid="{{.Id}}"> | |||||
<div class="item" data-text="Admin" data-value="3">Admin</div> | |||||
<div class="item" data-text="Write" data-value="2">Write</div> | |||||
<div class="item" data-text="Read" data-value="1">Read</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="ui two wide column"> | |||||
<button class="ui red tiny button inline text-thin delete-button" data-url="{{$.Link}}/delete" data-id="{{.Id}}"> | |||||
{{$.i18n.Tr "repo.settings.delete_collaborator"}} | |||||
</button> | |||||
</div> | |||||
</div> | </div> | ||||
{{end}} | {{end}} | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="ui small basic delete modal"> | |||||
<div class="ui icon header"> | |||||
<i class="trash icon"></i> | |||||
{{.i18n.Tr "repo.settings.collaborator_deletion"}} | |||||
</div> | |||||
<div class="content"> | |||||
<p>{{.i18n.Tr "repo.settings.collaborator_deletion_desc"}}</p> | |||||
</div> | |||||
{{template "base/delete_modal_actions" .}} | |||||
</div> | |||||
{{template "base/footer" .}} | {{template "base/footer" .}} |