Collaborators have write access as default, and can be changed via repository collaboration settings page to change between read, write and admin.tags/v1.21.12.1
| @@ -190,6 +190,8 @@ func runWeb(ctx *cli.Context) { | |||||
| 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) | ||||
| @@ -400,7 +402,11 @@ func runWeb(ctx *cli.Context) { | |||||
| 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) | ||||
| @@ -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. | 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] | ||||
| @@ -615,6 +613,9 @@ settings.transfer_succeed = Repository ownership has been transferred successful | |||||
| 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. | ||||
| @@ -949,6 +950,7 @@ auths.update = Update Authentication Setting | |||||
| 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 | ||||
| @@ -13,11 +13,11 @@ import ( | |||||
| 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 | ||||
| @@ -151,15 +151,14 @@ func (repo *Repository) refreshAccesses(e Engine, accessMap map[int64]AccessMode | |||||
| 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 | ||||
| } | } | ||||
| @@ -121,7 +121,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err | |||||
| 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, | ||||
| @@ -179,6 +179,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err | |||||
| 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 { | ||||
| @@ -330,7 +330,6 @@ func (repo *Repository) RepoRelLink() string { | |||||
| 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) | ||||
| } | } | ||||
| @@ -1797,105 +1796,6 @@ func CheckRepoStats() { | |||||
| // ***** 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() | |||||
| } | |||||
| // __ __ __ .__ | // __ __ __ .__ | ||||
| // / \ / \_____ _/ |_ ____ | |__ | // / \ / \_____ _/ |_ ____ | |__ | ||||
| // \ \/\/ /\__ \\ __\/ ___\| | \ | // \ \/\/ /\__ \\ __\/ ___\| | \ | ||||
| @@ -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() | |||||
| } | |||||
| @@ -20,15 +20,6 @@ | |||||
| "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, | ||||
| @@ -5,7 +5,7 @@ | |||||
| 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; | ||||
| } | } | ||||
| @@ -104,6 +104,9 @@ code.wrap { | |||||
| .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; | ||||
| } | } | ||||
| @@ -234,6 +237,10 @@ code.wrap { | |||||
| .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; | ||||
| @@ -1984,10 +1991,11 @@ footer .container .links > *:first-child { | |||||
| .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 { | ||||
| @@ -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() { | function initWiki() { | ||||
| if ($('.repository.wiki').length == 0) { | if ($('.repository.wiki').length == 0) { | ||||
| return; | return; | ||||
| @@ -964,7 +978,8 @@ $(document).ready(function () { | |||||
| initAdmin(); | initAdmin(); | ||||
| var routes = { | var routes = { | ||||
| 'div.user.settings': initUserSettings | |||||
| 'div.user.settings': initUserSettings, | |||||
| 'div.repository.settings.collaboration': initRepositoryCollaboration | |||||
| }; | }; | ||||
| var selector; | var selector; | ||||
| @@ -1,7 +1,7 @@ | |||||
| @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; | ||||
| } | } | ||||
| @@ -109,6 +109,12 @@ pre, code { | |||||
| } | } | ||||
| } | } | ||||
| &.form { | |||||
| .ui.button { | |||||
| font-weight: normal; | |||||
| } | |||||
| } | |||||
| .text { | .text { | ||||
| &.red { | &.red { | ||||
| color: #d95c5c !important; | color: #d95c5c !important; | ||||
| @@ -260,6 +266,11 @@ pre, code { | |||||
| margin-right: 4px; | margin-right: 4px; | ||||
| } | } | ||||
| } | } | ||||
| &.inline.delete-button { | |||||
| padding: 8px 15px; | |||||
| font-weight: normal; | |||||
| } | |||||
| } | } | ||||
| .overflow.menu { | .overflow.menu { | ||||
| @@ -1026,8 +1026,9 @@ | |||||
| .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; | ||||
| @@ -5,6 +5,8 @@ | |||||
| 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" | ||||
| @@ -218,11 +220,13 @@ func DeleteAuthSource(ctx *middleware.Context) { | |||||
| 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) | ||||
| @@ -257,30 +257,13 @@ func Collaboration(ctx *middleware.Context) { | |||||
| 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) | ||||
| } | } | ||||
| @@ -332,6 +315,26 @@ func CollaborationPost(ctx *middleware.Context) { | |||||
| 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 { | ||||
| @@ -11,14 +11,30 @@ | |||||
| </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> | ||||
| @@ -40,4 +56,15 @@ | |||||
| </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" .}} | ||||