Kaynağa Gözat

#1146 finsih UI work for access mode of collaborators

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
Unknwon 8 yıl önce
ebeveyn
işleme
045f14fbd0

+ 7
- 1
cmd/web.go Dosyayı Görüntüle

@@ -190,6 +190,8 @@ func runWeb(ctx *cli.Context) {

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.
m.Get("/", ignSignIn, routers.Home)
m.Get("/explore", ignSignIn, routers.Explore)
@@ -400,7 +402,11 @@ func runWeb(ctx *cli.Context) {
m.Group("/settings", func() {
m.Combo("").Get(repo.Settings).
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.Get("", repo.Webhooks)

+ 4
- 2
conf/locale/locale_en-US.ini Dosyayı Görüntüle

@@ -221,8 +221,6 @@ still_own_repo = Your account still has ownership over at least one repository,
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.

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.

[user]
@@ -615,6 +613,9 @@ settings.transfer_succeed = Repository ownership has been transferred successful
settings.confirm_delete = Confirm Deletion
settings.add_collaborator = Add New Collaborator
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.search_user_placeholder = Search user...
settings.org_not_allowed_to_be_collaborator = Organization is not allowed to be added as a collaborator.
@@ -949,6 +950,7 @@ auths.update = Update Authentication Setting
auths.delete = Delete This Authentication
auths.delete_auth_title = Authentication Deletion
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!

config.server_config = Server Configuration

+ 10
- 11
models/access.go Dosyayı Görüntüle

@@ -13,11 +13,11 @@ import (
type AccessMode int

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
@@ -151,15 +151,14 @@ func (repo *Repository) refreshAccesses(e Engine, accessMap map[int64]AccessMode
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 {
collaborators, err := repo.getCollaborators(e)
collaborations, err := repo.getCollaborations(e)
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
}

+ 2
- 1
models/issue_comment.go Dosyayı Görüntüle

@@ -121,7 +121,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, 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.
act := &Action{
ActUserID: opts.Doer.Id,
@@ -179,6 +179,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
if err != nil {
return nil, err
}

case COMMENT_TYPE_CLOSE:
act.OpType = ACTION_CLOSE_ISSUE
if opts.Issue.IsPull {

+ 0
- 100
models/repo.go Dosyayı Görüntüle

@@ -330,7 +330,6 @@ func (repo *Repository) RepoRelLink() string {
return "/" + repo.MustOwner().Name + "/" + repo.Name
}


func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) string {
return fmt.Sprintf("%s/%s/compare/%s...%s", repo.MustOwner().Name, repo.Name, oldCommitID, newCommitID)
}
@@ -1797,105 +1796,6 @@ func CheckRepoStats() {
// ***** 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()
}

// __ __ __ .__
// / \ / \_____ _/ |_ ____ | |__
// \ \/\/ /\__ \\ __\/ ___\| | \

+ 161
- 0
models/repo_collaboration.go Dosyayı Görüntüle

@@ -0,0 +1,161 @@
// 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()
}

+ 2
- 2
modules/bindata/bindata.go
Dosya farkı çok büyük olduğundan ihmal edildi
Dosyayı Görüntüle


+ 0
- 9
public/config.codekit Dosyayı Görüntüle

@@ -20,15 +20,6 @@
"outputPathIsOutsideProject": 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": {
"fileType": 16,
"ignore": 0,

+ 12
- 4
public/css/gogs.css Dosyayı Görüntüle

@@ -5,7 +5,7 @@
background-size: contain;
}
body {
font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif, '微软雅黑';
font-family: "Helvetica Neue", "Microsoft YaHei", Arial, Helvetica, sans-serif !important;
background-color: #fff;
overflow-y: scroll;
}
@@ -104,6 +104,9 @@ code.wrap {
.ui.container.fluid.padded {
padding: 0 10px 0 10px;
}
.ui.form .ui.button {
font-weight: normal;
}
.ui .text.red {
color: #d95c5c !important;
}
@@ -234,6 +237,10 @@ code.wrap {
.ui.status.buttons .octicon {
margin-right: 4px;
}
.ui.inline.delete-button {
padding: 8px 15px;
font-weight: normal;
}
.overflow.menu .items {
max-height: 300px;
overflow-y: auto;
@@ -1984,10 +1991,11 @@ footer .container .links > *:first-child {
.repository.settings.collaboration .collaborator.list {
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;
}
.repository.settings.collaboration #repo-collab-form #search-user-box .results {

+ 0
- 1
public/css/gogs.min.css
Dosya farkı çok büyük olduğundan ihmal edildi
Dosyayı Görüntüle


+ 16
- 1
public/js/gogs.js Dosyayı Görüntüle

@@ -458,6 +458,20 @@ function initRepository() {
}
}

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() {
if ($('.repository.wiki').length == 0) {
return;
@@ -964,7 +978,8 @@ $(document).ready(function () {
initAdmin();

var routes = {
'div.user.settings': initUserSettings
'div.user.settings': initUserSettings,
'div.repository.settings.collaboration': initRepositoryCollaboration
};

var selector;

+ 12
- 1
public/less/_base.less Dosyayı Görüntüle

@@ -1,7 +1,7 @@
@footer-margin: 40px;

body {
font-family: 'Helvetica Neue',Arial,Helvetica,sans-serif,'微软雅黑';
font-family: "Helvetica Neue", "Microsoft YaHei", Arial, Helvetica, sans-serif !important;
background-color: #fff;
overflow-y: scroll;
}
@@ -109,6 +109,12 @@ pre, code {
}
}

&.form {
.ui.button {
font-weight: normal;
}
}

.text {
&.red {
color: #d95c5c !important;
@@ -260,6 +266,11 @@ pre, code {
margin-right: 4px;
}
}

&.inline.delete-button {
padding: 8px 15px;
font-weight: normal;
}
}

.overflow.menu {

+ 3
- 2
public/less/_repository.less Dosyayı Görüntüle

@@ -1026,8 +1026,9 @@
.collaborator.list {
padding: 0;

.item {
padding: 10px 20px;
>.item {
margin: 0;
line-height: 2em;

&:not(:last-child) {
border-bottom: 1px solid #DDD;

+ 7
- 3
routers/admin/auths.go Dosyayı Görüntüle

@@ -5,6 +5,8 @@
package admin

import (
"fmt"

"github.com/Unknwon/com"
"github.com/go-xorm/core"

@@ -218,11 +220,13 @@ func DeleteAuthSource(ctx *middleware.Context) {
if err = models.DeleteSource(source); err != nil {
switch err {
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:
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
}
log.Trace("Authentication deleted by admin(%s): %d", ctx.User.Name, source.ID)

+ 21
- 18
routers/repo/setting.go Dosyayı Görüntüle

@@ -257,30 +257,13 @@ func Collaboration(ctx *middleware.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings")
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()
if err != nil {
ctx.Handle(500, "GetCollaborators", err)
return
}

ctx.Data["Collaborators"] = users

ctx.HTML(200, COLLABORATION)
}

@@ -332,6 +315,26 @@ func CollaborationPost(ctx *middleware.Context) {
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) {
owner, err := models.GetUserByName(ctx.Params(":username"))
if err != nil {

+ 35
- 8
templates/repo/settings/collaboration.tmpl Dosyayı Görüntüle

@@ -11,14 +11,30 @@
</h4>
<div class="ui attached segment collaborator list">
{{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>
{{end}}
</div>
@@ -40,4 +56,15 @@
</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" .}}

Loading…
İptal
Kaydet