Add teams to repo on collaboration page. (#8045)
* Add teams to repo on collaboration page. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Add option for repository admins to change teams access to repo. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Add comment for functions Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Make RepoAdminChangeTeamAccess default false in xorm and make it default checked in template instead. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Make proper language strings and fix error redirection. * Add unit tests for adding and deleting team from repository. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Add database migration Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Fix redirect Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Fix locale string mismatch. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Move team access mode text logic to template. * Move collaborator access mode text logic to template.
This commit is contained in:
parent
63ff61615e
commit
a0e88dfc2e
30 changed files with 575 additions and 79 deletions
|
@ -1370,6 +1370,23 @@ func (err ErrTeamAlreadyExist) Error() string {
|
||||||
return fmt.Sprintf("team already exists [org_id: %d, name: %s]", err.OrgID, err.Name)
|
return fmt.Sprintf("team already exists [org_id: %d, name: %s]", err.OrgID, err.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrTeamNotExist represents a "TeamNotExist" error
|
||||||
|
type ErrTeamNotExist struct {
|
||||||
|
OrgID int64
|
||||||
|
TeamID int64
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrTeamNotExist checks if an error is a ErrTeamNotExist.
|
||||||
|
func IsErrTeamNotExist(err error) bool {
|
||||||
|
_, ok := err.(ErrTeamNotExist)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrTeamNotExist) Error() string {
|
||||||
|
return fmt.Sprintf("team does not exist [org_id %d, team_id %d, name: %s]", err.OrgID, err.TeamID, err.Name)
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Two-factor authentication
|
// Two-factor authentication
|
||||||
//
|
//
|
||||||
|
|
|
@ -509,3 +509,14 @@
|
||||||
num_forks: 0
|
num_forks: 0
|
||||||
num_issues: 0
|
num_issues: 0
|
||||||
is_mirror: false
|
is_mirror: false
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 43
|
||||||
|
owner_id: 26
|
||||||
|
lower_name: repo26
|
||||||
|
name: repo26
|
||||||
|
is_private: true
|
||||||
|
num_stars: 0
|
||||||
|
num_forks: 0
|
||||||
|
num_issues: 0
|
||||||
|
is_mirror: false
|
|
@ -87,3 +87,12 @@
|
||||||
authorize: 1 # owner
|
authorize: 1 # owner
|
||||||
num_repos: 0
|
num_repos: 0
|
||||||
num_members: 1
|
num_members: 1
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 11
|
||||||
|
org_id: 26
|
||||||
|
lower_name: team11
|
||||||
|
name: team11
|
||||||
|
authorize: 1 # read
|
||||||
|
num_repos: 0
|
||||||
|
num_members: 0
|
||||||
|
|
|
@ -410,3 +410,21 @@
|
||||||
num_repos: 0
|
num_repos: 0
|
||||||
num_members: 1
|
num_members: 1
|
||||||
num_teams: 1
|
num_teams: 1
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 26
|
||||||
|
lower_name: org26
|
||||||
|
name: org26
|
||||||
|
full_name: "Org26"
|
||||||
|
email: org26@example.com
|
||||||
|
email_notifications_preference: onmention
|
||||||
|
passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
|
||||||
|
type: 1 # organization
|
||||||
|
salt: ZogKvWdyEx
|
||||||
|
is_admin: false
|
||||||
|
avatar: avatar26
|
||||||
|
avatar_email: org26@example.com
|
||||||
|
num_repos: 1
|
||||||
|
num_members: 0
|
||||||
|
num_teams: 1
|
||||||
|
repo_admin_change_team_access: true
|
|
@ -248,6 +248,8 @@ var migrations = []Migration{
|
||||||
NewMigration("add table columns for cross referencing issues", addCrossReferenceColumns),
|
NewMigration("add table columns for cross referencing issues", addCrossReferenceColumns),
|
||||||
// v96 -> v97
|
// v96 -> v97
|
||||||
NewMigration("delete orphaned attachments", deleteOrphanedAttachments),
|
NewMigration("delete orphaned attachments", deleteOrphanedAttachments),
|
||||||
|
// v97 -> v98
|
||||||
|
NewMigration("add repo_admin_change_team_access to user", addRepoAdminChangeTeamAccessColumnForUser),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate database to current version
|
// Migrate database to current version
|
||||||
|
|
15
models/migrations/v97.go
Normal file
15
models/migrations/v97.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// Copyright 2019 The Gitea 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 migrations
|
||||||
|
|
||||||
|
import "github.com/go-xorm/xorm"
|
||||||
|
|
||||||
|
func addRepoAdminChangeTeamAccessColumnForUser(x *xorm.Engine) error {
|
||||||
|
type User struct {
|
||||||
|
RepoAdminChangeTeamAccess bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
}
|
||||||
|
|
||||||
|
return x.Sync2(new(User))
|
||||||
|
}
|
|
@ -6,7 +6,6 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -20,11 +19,6 @@ import (
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrTeamNotExist team does not exist
|
|
||||||
ErrTeamNotExist = errors.New("Team does not exist")
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsOwnedBy returns true if given user is in the owner team.
|
// IsOwnedBy returns true if given user is in the owner team.
|
||||||
func (org *User) IsOwnedBy(uid int64) (bool, error) {
|
func (org *User) IsOwnedBy(uid int64) (bool, error) {
|
||||||
return IsOrganizationOwner(org.ID, uid)
|
return IsOrganizationOwner(org.ID, uid)
|
||||||
|
@ -304,7 +298,7 @@ type OrgUser struct {
|
||||||
func isOrganizationOwner(e Engine, orgID, uid int64) (bool, error) {
|
func isOrganizationOwner(e Engine, orgID, uid int64) (bool, error) {
|
||||||
ownerTeam, err := getOwnerTeam(e, orgID)
|
ownerTeam, err := getOwnerTeam(e, orgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == ErrTeamNotExist {
|
if IsErrTeamNotExist(err) {
|
||||||
log.Error("Organization does not have owner team: %d", orgID)
|
log.Error("Organization does not have owner team: %d", orgID)
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -352,7 +352,7 @@ func getTeam(e Engine, orgID int64, name string) (*Team, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if !has {
|
} else if !has {
|
||||||
return nil, ErrTeamNotExist
|
return nil, ErrTeamNotExist{orgID, 0, name}
|
||||||
}
|
}
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
@ -373,7 +373,7 @@ func getTeamByID(e Engine, teamID int64) (*Team, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if !has {
|
} else if !has {
|
||||||
return nil, ErrTeamNotExist
|
return nil, ErrTeamNotExist{0, teamID, ""}
|
||||||
}
|
}
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,11 +64,11 @@ func TestUser_GetTeam(t *testing.T) {
|
||||||
assert.Equal(t, "team1", team.LowerName)
|
assert.Equal(t, "team1", team.LowerName)
|
||||||
|
|
||||||
_, err = org.GetTeam("does not exist")
|
_, err = org.GetTeam("does not exist")
|
||||||
assert.Equal(t, ErrTeamNotExist, err)
|
assert.True(t, IsErrTeamNotExist(err))
|
||||||
|
|
||||||
nonOrg := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
nonOrg := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
||||||
_, err = nonOrg.GetTeam("team")
|
_, err = nonOrg.GetTeam("team")
|
||||||
assert.Equal(t, ErrTeamNotExist, err)
|
assert.True(t, IsErrTeamNotExist(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUser_GetOwnerTeam(t *testing.T) {
|
func TestUser_GetOwnerTeam(t *testing.T) {
|
||||||
|
@ -80,7 +80,7 @@ func TestUser_GetOwnerTeam(t *testing.T) {
|
||||||
|
|
||||||
nonOrg := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
nonOrg := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User)
|
||||||
_, err = nonOrg.GetOwnerTeam()
|
_, err = nonOrg.GetOwnerTeam()
|
||||||
assert.Equal(t, ErrTeamNotExist, err)
|
assert.True(t, IsErrTeamNotExist(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUser_GetTeams(t *testing.T) {
|
func TestUser_GetTeams(t *testing.T) {
|
||||||
|
|
|
@ -16,20 +16,6 @@ type Collaboration struct {
|
||||||
Mode AccessMode `xorm:"DEFAULT 2 NOT NULL"`
|
Mode AccessMode `xorm:"DEFAULT 2 NOT NULL"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ModeI18nKey returns the collaboration mode I18n Key
|
|
||||||
func (c *Collaboration) ModeI18nKey() string {
|
|
||||||
switch c.Mode {
|
|
||||||
case AccessModeRead:
|
|
||||||
return "repo.settings.collaboration.read"
|
|
||||||
case AccessModeWrite:
|
|
||||||
return "repo.settings.collaboration.write"
|
|
||||||
case AccessModeAdmin:
|
|
||||||
return "repo.settings.collaboration.admin"
|
|
||||||
default:
|
|
||||||
return "repo.settings.collaboration.undefined"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddCollaborator adds new collaboration to a repository with default access mode.
|
// AddCollaborator adds new collaboration to a repository with default access mode.
|
||||||
func (repo *Repository) AddCollaborator(u *User) error {
|
func (repo *Repository) AddCollaborator(u *User) error {
|
||||||
collaboration := &Collaboration{
|
collaboration := &Collaboration{
|
||||||
|
@ -183,3 +169,17 @@ func (repo *Repository) DeleteCollaboration(uid int64) (err error) {
|
||||||
|
|
||||||
return sess.Commit()
|
return sess.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *Repository) getRepoTeams(e Engine) (teams []*Team, err error) {
|
||||||
|
return teams, e.
|
||||||
|
Join("INNER", "team_repo", "team_repo.team_id = team.id").
|
||||||
|
Where("team.org_id = ?", repo.OwnerID).
|
||||||
|
And("team_repo.repo_id=?", repo.ID).
|
||||||
|
OrderBy("CASE WHEN name LIKE '" + ownerTeamName + "' THEN '' ELSE name END").
|
||||||
|
Find(&teams)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRepoTeams gets the list of teams that has access to the repository
|
||||||
|
func (repo *Repository) GetRepoTeams() ([]*Team, error) {
|
||||||
|
return repo.getRepoTeams(x)
|
||||||
|
}
|
||||||
|
|
|
@ -10,17 +10,6 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCollaboration_ModeI18nKey(t *testing.T) {
|
|
||||||
assert.Equal(t, "repo.settings.collaboration.read",
|
|
||||||
(&Collaboration{Mode: AccessModeRead}).ModeI18nKey())
|
|
||||||
assert.Equal(t, "repo.settings.collaboration.write",
|
|
||||||
(&Collaboration{Mode: AccessModeWrite}).ModeI18nKey())
|
|
||||||
assert.Equal(t, "repo.settings.collaboration.admin",
|
|
||||||
(&Collaboration{Mode: AccessModeAdmin}).ModeI18nKey())
|
|
||||||
assert.Equal(t, "repo.settings.collaboration.undefined",
|
|
||||||
(&Collaboration{Mode: AccessModeNone}).ModeI18nKey())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRepository_AddCollaborator(t *testing.T) {
|
func TestRepository_AddCollaborator(t *testing.T) {
|
||||||
assert.NoError(t, PrepareTestDatabase())
|
assert.NoError(t, PrepareTestDatabase())
|
||||||
|
|
||||||
|
|
|
@ -153,6 +153,7 @@ type User struct {
|
||||||
Members UserList `xorm:"-"`
|
Members UserList `xorm:"-"`
|
||||||
MembersIsPublic map[int64]bool `xorm:"-"`
|
MembersIsPublic map[int64]bool `xorm:"-"`
|
||||||
Visibility structs.VisibleType `xorm:"NOT NULL DEFAULT 0"`
|
Visibility structs.VisibleType `xorm:"NOT NULL DEFAULT 0"`
|
||||||
|
RepoAdminChangeTeamAccess bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
|
||||||
// Preferences
|
// Preferences
|
||||||
DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"`
|
DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"`
|
||||||
|
|
|
@ -140,7 +140,10 @@ func TestSearchUsers(t *testing.T) {
|
||||||
testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 3, PageSize: 2},
|
testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 3, PageSize: 2},
|
||||||
[]int64{19, 25})
|
[]int64{19, 25})
|
||||||
|
|
||||||
testOrgSuccess(&SearchUserOptions{Page: 4, PageSize: 2},
|
testOrgSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 4, PageSize: 2},
|
||||||
|
[]int64{26})
|
||||||
|
|
||||||
|
testOrgSuccess(&SearchUserOptions{Page: 5, PageSize: 2},
|
||||||
[]int64{})
|
[]int64{})
|
||||||
|
|
||||||
// test users
|
// test users
|
||||||
|
|
|
@ -43,7 +43,7 @@ func (users UserList) loadOrganizationOwners(e Engine, orgID int64) (map[int64]*
|
||||||
}
|
}
|
||||||
ownerTeam, err := getOwnerTeam(e, orgID)
|
ownerTeam, err := getOwnerTeam(e, orgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == ErrTeamNotExist {
|
if IsErrTeamNotExist(err) {
|
||||||
log.Error("Organization does not have owner team: %d", orgID)
|
log.Error("Organization does not have owner team: %d", orgID)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ type UpdateOrgSettingForm struct {
|
||||||
Location string `binding:"MaxSize(50)"`
|
Location string `binding:"MaxSize(50)"`
|
||||||
Visibility structs.VisibleType
|
Visibility structs.VisibleType
|
||||||
MaxRepoCreation int
|
MaxRepoCreation int
|
||||||
|
RepoAdminChangeTeamAccess bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates the fields
|
// Validate validates the fields
|
||||||
|
|
|
@ -14,6 +14,7 @@ type Organization struct {
|
||||||
Website string `json:"website"`
|
Website string `json:"website"`
|
||||||
Location string `json:"location"`
|
Location string `json:"location"`
|
||||||
Visibility string `json:"visibility"`
|
Visibility string `json:"visibility"`
|
||||||
|
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateOrgOption options for creating an organization
|
// CreateOrgOption options for creating an organization
|
||||||
|
@ -27,6 +28,7 @@ type CreateOrgOption struct {
|
||||||
// possible values are `public` (default), `limited` or `private`
|
// possible values are `public` (default), `limited` or `private`
|
||||||
// enum: public,limited,private
|
// enum: public,limited,private
|
||||||
Visibility string `json:"visibility" binding:"In(,public,limited,private)"`
|
Visibility string `json:"visibility" binding:"In(,public,limited,private)"`
|
||||||
|
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditOrgOption options for editing an organization
|
// EditOrgOption options for editing an organization
|
||||||
|
@ -38,4 +40,5 @@ type EditOrgOption struct {
|
||||||
// possible values are `public`, `limited` or `private`
|
// possible values are `public`, `limited` or `private`
|
||||||
// enum: public,limited,private
|
// enum: public,limited,private
|
||||||
Visibility string `json:"visibility" binding:"In(,public,limited,private)"`
|
Visibility string `json:"visibility" binding:"In(,public,limited,private)"`
|
||||||
|
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -319,6 +319,7 @@ enterred_invalid_repo_name = The repository name you entered is incorrect.
|
||||||
enterred_invalid_owner_name = The new owner name is not valid.
|
enterred_invalid_owner_name = The new owner name is not valid.
|
||||||
enterred_invalid_password = The password you entered is incorrect.
|
enterred_invalid_password = The password you entered is incorrect.
|
||||||
user_not_exist = The user does not exist.
|
user_not_exist = The user does not exist.
|
||||||
|
team_not_exist = The team does not exist.
|
||||||
last_org_owner = You cannot remove the last user from the 'owners' team. There must be at least one owner in any given team.
|
last_org_owner = You cannot remove the last user from the 'owners' team. There must be at least one owner in any given team.
|
||||||
cannot_add_org_to_team = An organization cannot be added as a team member.
|
cannot_add_org_to_team = An organization cannot be added as a team member.
|
||||||
|
|
||||||
|
@ -1136,6 +1137,7 @@ settings.collaboration = Collaborators
|
||||||
settings.collaboration.admin = Administrator
|
settings.collaboration.admin = Administrator
|
||||||
settings.collaboration.write = Write
|
settings.collaboration.write = Write
|
||||||
settings.collaboration.read = Read
|
settings.collaboration.read = Read
|
||||||
|
settings.collaboration.owner = Owner
|
||||||
settings.collaboration.undefined = Undefined
|
settings.collaboration.undefined = Undefined
|
||||||
settings.hooks = Webhooks
|
settings.hooks = Webhooks
|
||||||
settings.githooks = Git Hooks
|
settings.githooks = Git Hooks
|
||||||
|
@ -1217,6 +1219,11 @@ settings.collaborator_deletion_desc = Removing a collaborator will revoke their
|
||||||
settings.remove_collaborator_success = The collaborator has been removed.
|
settings.remove_collaborator_success = The collaborator has been removed.
|
||||||
settings.search_user_placeholder = Search user…
|
settings.search_user_placeholder = Search user…
|
||||||
settings.org_not_allowed_to_be_collaborator = Organizations cannot be added as a collaborator.
|
settings.org_not_allowed_to_be_collaborator = Organizations cannot be added as a collaborator.
|
||||||
|
settings.change_team_access_not_allowed = Changing team access for repository has been restricted to organization owner
|
||||||
|
settings.team_not_in_organization = The team is not in the same organization as the repository
|
||||||
|
settings.add_team_duplicate = Team already has the repository
|
||||||
|
settings.add_team_success = The team now have access to the repository.
|
||||||
|
settings.remove_team_success = The team's access to the repository has been removed.
|
||||||
settings.add_webhook = Add Webhook
|
settings.add_webhook = Add Webhook
|
||||||
settings.add_webhook.invalid_channel_name = Webhook channel name cannot be empty and cannot contain only a # character.
|
settings.add_webhook.invalid_channel_name = Webhook channel name cannot be empty and cannot contain only a # character.
|
||||||
settings.hooks_desc = Webhooks automatically make HTTP POST requests to a server when certain Gitea events trigger. Read more in the <a target="_blank" rel="noopener noreferrer" href="%s">webhooks guide</a>.
|
settings.hooks_desc = Webhooks automatically make HTTP POST requests to a server when certain Gitea events trigger. Read more in the <a target="_blank" rel="noopener noreferrer" href="%s">webhooks guide</a>.
|
||||||
|
@ -1475,6 +1482,8 @@ settings.options = Organization
|
||||||
settings.full_name = Full Name
|
settings.full_name = Full Name
|
||||||
settings.website = Website
|
settings.website = Website
|
||||||
settings.location = Location
|
settings.location = Location
|
||||||
|
settings.permission = Permissions
|
||||||
|
settings.repoadminchangeteam = Repository admin can add and remove access for teams
|
||||||
settings.visibility = Visibility
|
settings.visibility = Visibility
|
||||||
settings.visibility.public = Public
|
settings.visibility.public = Public
|
||||||
settings.visibility.limited = Limited (Visible to logged in users only)
|
settings.visibility.limited = Limited (Visible to logged in users only)
|
||||||
|
|
|
@ -747,6 +747,8 @@ footer .ui.left,footer .ui.right{line-height:40px}
|
||||||
.repository.settings.collaboration .collaborator.list>.item:not(:last-child){border-bottom:1px solid #ddd}
|
.repository.settings.collaboration .collaborator.list>.item:not(:last-child){border-bottom:1px solid #ddd}
|
||||||
.repository.settings.collaboration #repo-collab-form #search-user-box .results{left:7px}
|
.repository.settings.collaboration #repo-collab-form #search-user-box .results{left:7px}
|
||||||
.repository.settings.collaboration #repo-collab-form .ui.button{margin-left:5px;margin-top:-3px}
|
.repository.settings.collaboration #repo-collab-form .ui.button{margin-left:5px;margin-top:-3px}
|
||||||
|
.repository.settings.collaboration #repo-collab-team-form #search-team-box .results{left:7px}
|
||||||
|
.repository.settings.collaboration #repo-collab-team-form .ui.button{margin-left:5px;margin-top:-3px}
|
||||||
.repository.settings.branches .protected-branches .selection.dropdown{width:300px}
|
.repository.settings.branches .protected-branches .selection.dropdown{width:300px}
|
||||||
.repository.settings.branches .protected-branches .item{border:1px solid #eaeaea;padding:10px 15px}
|
.repository.settings.branches .protected-branches .item{border:1px solid #eaeaea;padding:10px 15px}
|
||||||
.repository.settings.branches .protected-branches .item:not(:last-child){border-bottom:0}
|
.repository.settings.branches .protected-branches .item:not(:last-child){border-bottom:0}
|
||||||
|
@ -783,6 +785,7 @@ footer .ui.left,footer .ui.right{line-height:40px}
|
||||||
.user-cards .list .item .meta{margin-top:5px}
|
.user-cards .list .item .meta{margin-top:5px}
|
||||||
#search-repo-box .results .result .image,#search-user-box .results .result .image{float:left;margin-right:8px;width:2em;height:2em}
|
#search-repo-box .results .result .image,#search-user-box .results .result .image{float:left;margin-right:8px;width:2em;height:2em}
|
||||||
#search-repo-box .results .result .content,#search-user-box .results .result .content{margin:6px 0}
|
#search-repo-box .results .result .content,#search-user-box .results .result .content{margin:6px 0}
|
||||||
|
#search-team-box .results .result .content{margin:6px 0}
|
||||||
#issue-filters.hide{display:none}
|
#issue-filters.hide{display:none}
|
||||||
#issue-actions{margin-top:-1rem!important}
|
#issue-actions{margin-top:-1rem!important}
|
||||||
#issue-actions.hide{display:none}
|
#issue-actions.hide{display:none}
|
||||||
|
|
|
@ -1761,6 +1761,30 @@ function searchUsers() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function searchTeams() {
|
||||||
|
const $searchTeamBox = $('#search-team-box');
|
||||||
|
$searchTeamBox.search({
|
||||||
|
minCharacters: 2,
|
||||||
|
apiSettings: {
|
||||||
|
url: suburl + '/api/v1/orgs/' + $searchTeamBox.data('org') + '/teams',
|
||||||
|
headers: {"X-Csrf-Token": csrf},
|
||||||
|
onResponse: function(response) {
|
||||||
|
const items = [];
|
||||||
|
$.each(response, function (_i, item) {
|
||||||
|
const title = item.name + ' (' + item.permission + ' access)';
|
||||||
|
items.push({
|
||||||
|
title: title,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
return { results: items }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
searchFields: ['name', 'description'],
|
||||||
|
showNoResults: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function searchRepositories() {
|
function searchRepositories() {
|
||||||
const $searchRepoBox = $('#search-repo-box');
|
const $searchRepoBox = $('#search-repo-box');
|
||||||
$searchRepoBox.search({
|
$searchRepoBox.search({
|
||||||
|
@ -2171,6 +2195,7 @@ $(document).ready(function () {
|
||||||
|
|
||||||
buttonsClickOnEnter();
|
buttonsClickOnEnter();
|
||||||
searchUsers();
|
searchUsers();
|
||||||
|
searchTeams();
|
||||||
searchRepositories();
|
searchRepositories();
|
||||||
|
|
||||||
initCommentForm();
|
initCommentForm();
|
||||||
|
|
|
@ -1736,6 +1736,19 @@
|
||||||
margin-top: -3px;
|
margin-top: -3px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#repo-collab-team-form {
|
||||||
|
#search-team-box {
|
||||||
|
.results {
|
||||||
|
left: 7px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.button {
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-top: -3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.branches {
|
&.branches {
|
||||||
|
@ -1936,6 +1949,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#search-team-box {
|
||||||
|
.results {
|
||||||
|
.result {
|
||||||
|
.content {
|
||||||
|
margin: 6px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#issue-filters.hide {
|
#issue-filters.hide {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -214,6 +214,7 @@ func ToOrganization(org *models.User) *api.Organization {
|
||||||
Website: org.Website,
|
Website: org.Website,
|
||||||
Location: org.Location,
|
Location: org.Location,
|
||||||
Visibility: org.Visibility.String(),
|
Visibility: org.Visibility.String(),
|
||||||
|
RepoAdminChangeTeamAccess: org.RepoAdminChangeTeamAccess,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -103,6 +103,7 @@ func Create(ctx *context.APIContext, form api.CreateOrgOption) {
|
||||||
IsActive: true,
|
IsActive: true,
|
||||||
Type: models.UserTypeOrganization,
|
Type: models.UserTypeOrganization,
|
||||||
Visibility: visibility,
|
Visibility: visibility,
|
||||||
|
RepoAdminChangeTeamAccess: form.RepoAdminChangeTeamAccess,
|
||||||
}
|
}
|
||||||
if err := models.CreateOrganization(org, ctx.User); err != nil {
|
if err := models.CreateOrganization(org, ctx.User); err != nil {
|
||||||
if models.IsErrUserAlreadyExist(err) ||
|
if models.IsErrUserAlreadyExist(err) ||
|
||||||
|
|
|
@ -83,6 +83,7 @@ func SettingsPost(ctx *context.Context, form auth.UpdateOrgSettingForm) {
|
||||||
org.Website = form.Website
|
org.Website = form.Website
|
||||||
org.Location = form.Location
|
org.Location = form.Location
|
||||||
org.Visibility = form.Visibility
|
org.Visibility = form.Visibility
|
||||||
|
org.RepoAdminChangeTeamAccess = form.RepoAdminChangeTeamAccess
|
||||||
if err := models.UpdateUser(org); err != nil {
|
if err := models.UpdateUser(org); err != nil {
|
||||||
ctx.ServerError("UpdateUser", err)
|
ctx.ServerError("UpdateUser", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -490,6 +490,18 @@ func Collaboration(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
ctx.Data["Collaborators"] = users
|
ctx.Data["Collaborators"] = users
|
||||||
|
|
||||||
|
teams, err := ctx.Repo.Repository.GetRepoTeams()
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetRepoTeams", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Data["Teams"] = teams
|
||||||
|
ctx.Data["Repo"] = ctx.Repo.Repository
|
||||||
|
ctx.Data["OrgID"] = ctx.Repo.Repository.OwnerID
|
||||||
|
ctx.Data["OrgName"] = ctx.Repo.Repository.OwnerName
|
||||||
|
ctx.Data["Org"] = ctx.Repo.Repository.Owner
|
||||||
|
ctx.Data["Units"] = models.Units
|
||||||
|
|
||||||
ctx.HTML(200, tplCollaboration)
|
ctx.HTML(200, tplCollaboration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -566,6 +578,77 @@ func DeleteCollaboration(ctx *context.Context) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddTeamPost response for adding a team to a repository
|
||||||
|
func AddTeamPost(ctx *context.Context) {
|
||||||
|
if !ctx.Repo.Owner.RepoAdminChangeTeamAccess && !ctx.Repo.IsOwner() {
|
||||||
|
ctx.Flash.Error(ctx.Tr("repo.settings.change_team_access_not_allowed"))
|
||||||
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.Query("team")))
|
||||||
|
if len(name) == 0 || ctx.Repo.Owner.LowerName == name {
|
||||||
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
team, err := ctx.Repo.Owner.GetTeam(name)
|
||||||
|
if err != nil {
|
||||||
|
if models.IsErrTeamNotExist(err) {
|
||||||
|
ctx.Flash.Error(ctx.Tr("form.team_not_exist"))
|
||||||
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||||
|
} else {
|
||||||
|
ctx.ServerError("GetTeam", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if team.OrgID != ctx.Repo.Repository.OwnerID {
|
||||||
|
ctx.Flash.Error(ctx.Tr("repo.settings.team_not_in_organization"))
|
||||||
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if models.HasTeamRepo(ctx.Repo.Repository.OwnerID, team.ID, ctx.Repo.Repository.ID) {
|
||||||
|
ctx.Flash.Error(ctx.Tr("repo.settings.add_team_duplicate"))
|
||||||
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = team.AddRepository(ctx.Repo.Repository); err != nil {
|
||||||
|
ctx.ServerError("team.AddRepository", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Flash.Success(ctx.Tr("repo.settings.add_team_success"))
|
||||||
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteTeam response for deleting a team from a repository
|
||||||
|
func DeleteTeam(ctx *context.Context) {
|
||||||
|
if !ctx.Repo.Owner.RepoAdminChangeTeamAccess && !ctx.Repo.IsOwner() {
|
||||||
|
ctx.Flash.Error(ctx.Tr("repo.settings.change_team_access_not_allowed"))
|
||||||
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
team, err := models.GetTeamByID(ctx.QueryInt64("id"))
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetTeamByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = team.RemoveRepository(ctx.Repo.Repository.ID); err != nil {
|
||||||
|
ctx.ServerError("team.RemoveRepositorys", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Flash.Success(ctx.Tr("repo.settings.remove_team_success"))
|
||||||
|
ctx.JSON(200, map[string]interface{}{
|
||||||
|
"redirect": ctx.Repo.RepoLink + "/settings/collaboration",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// parseOwnerAndRepo get repos by owner
|
// parseOwnerAndRepo get repos by owner
|
||||||
func parseOwnerAndRepo(ctx *context.Context) (*models.User, *models.Repository) {
|
func parseOwnerAndRepo(ctx *context.Context) (*models.User, *models.Repository) {
|
||||||
owner, err := models.GetUserByName(ctx.Params(":username"))
|
owner, err := models.GetUserByName(ctx.Params(":username"))
|
||||||
|
|
|
@ -185,3 +185,196 @@ func TestCollaborationPost_NonExistentUser(t *testing.T) {
|
||||||
assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
|
assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
|
||||||
assert.NotEmpty(t, ctx.Flash.ErrorMsg)
|
assert.NotEmpty(t, ctx.Flash.ErrorMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAddTeamPost(t *testing.T) {
|
||||||
|
models.PrepareTestEnv(t)
|
||||||
|
ctx := test.MockContext(t, "org26/repo43")
|
||||||
|
|
||||||
|
ctx.Req.Form.Set("team", "team11")
|
||||||
|
|
||||||
|
org := &models.User{
|
||||||
|
LowerName: "org26",
|
||||||
|
Type: models.UserTypeOrganization,
|
||||||
|
}
|
||||||
|
|
||||||
|
team := &models.Team{
|
||||||
|
ID: 11,
|
||||||
|
OrgID: 26,
|
||||||
|
}
|
||||||
|
|
||||||
|
re := &models.Repository{
|
||||||
|
ID: 43,
|
||||||
|
Owner: org,
|
||||||
|
OwnerID: 26,
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := &context.Repository{
|
||||||
|
Owner: &models.User{
|
||||||
|
ID: 26,
|
||||||
|
LowerName: "org26",
|
||||||
|
RepoAdminChangeTeamAccess: true,
|
||||||
|
},
|
||||||
|
Repository: re,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Repo = repo
|
||||||
|
|
||||||
|
AddTeamPost(ctx)
|
||||||
|
|
||||||
|
assert.True(t, team.HasRepository(re.ID))
|
||||||
|
assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
|
||||||
|
assert.Empty(t, ctx.Flash.ErrorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddTeamPost_NotAllowed(t *testing.T) {
|
||||||
|
models.PrepareTestEnv(t)
|
||||||
|
ctx := test.MockContext(t, "org26/repo43")
|
||||||
|
|
||||||
|
ctx.Req.Form.Set("team", "team11")
|
||||||
|
|
||||||
|
org := &models.User{
|
||||||
|
LowerName: "org26",
|
||||||
|
Type: models.UserTypeOrganization,
|
||||||
|
}
|
||||||
|
|
||||||
|
team := &models.Team{
|
||||||
|
ID: 11,
|
||||||
|
OrgID: 26,
|
||||||
|
}
|
||||||
|
|
||||||
|
re := &models.Repository{
|
||||||
|
ID: 43,
|
||||||
|
Owner: org,
|
||||||
|
OwnerID: 26,
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := &context.Repository{
|
||||||
|
Owner: &models.User{
|
||||||
|
ID: 26,
|
||||||
|
LowerName: "org26",
|
||||||
|
RepoAdminChangeTeamAccess: false,
|
||||||
|
},
|
||||||
|
Repository: re,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Repo = repo
|
||||||
|
|
||||||
|
AddTeamPost(ctx)
|
||||||
|
|
||||||
|
assert.False(t, team.HasRepository(re.ID))
|
||||||
|
assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
|
||||||
|
assert.NotEmpty(t, ctx.Flash.ErrorMsg)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddTeamPost_AddTeamTwice(t *testing.T) {
|
||||||
|
models.PrepareTestEnv(t)
|
||||||
|
ctx := test.MockContext(t, "org26/repo43")
|
||||||
|
|
||||||
|
ctx.Req.Form.Set("team", "team11")
|
||||||
|
|
||||||
|
org := &models.User{
|
||||||
|
LowerName: "org26",
|
||||||
|
Type: models.UserTypeOrganization,
|
||||||
|
}
|
||||||
|
|
||||||
|
team := &models.Team{
|
||||||
|
ID: 11,
|
||||||
|
OrgID: 26,
|
||||||
|
}
|
||||||
|
|
||||||
|
re := &models.Repository{
|
||||||
|
ID: 43,
|
||||||
|
Owner: org,
|
||||||
|
OwnerID: 26,
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := &context.Repository{
|
||||||
|
Owner: &models.User{
|
||||||
|
ID: 26,
|
||||||
|
LowerName: "org26",
|
||||||
|
RepoAdminChangeTeamAccess: true,
|
||||||
|
},
|
||||||
|
Repository: re,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Repo = repo
|
||||||
|
|
||||||
|
AddTeamPost(ctx)
|
||||||
|
|
||||||
|
AddTeamPost(ctx)
|
||||||
|
assert.True(t, team.HasRepository(re.ID))
|
||||||
|
assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
|
||||||
|
assert.NotEmpty(t, ctx.Flash.ErrorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddTeamPost_NonExistentTeam(t *testing.T) {
|
||||||
|
models.PrepareTestEnv(t)
|
||||||
|
ctx := test.MockContext(t, "org26/repo43")
|
||||||
|
|
||||||
|
ctx.Req.Form.Set("team", "team-non-existent")
|
||||||
|
|
||||||
|
org := &models.User{
|
||||||
|
LowerName: "org26",
|
||||||
|
Type: models.UserTypeOrganization,
|
||||||
|
}
|
||||||
|
|
||||||
|
re := &models.Repository{
|
||||||
|
ID: 43,
|
||||||
|
Owner: org,
|
||||||
|
OwnerID: 26,
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := &context.Repository{
|
||||||
|
Owner: &models.User{
|
||||||
|
ID: 26,
|
||||||
|
LowerName: "org26",
|
||||||
|
RepoAdminChangeTeamAccess: true,
|
||||||
|
},
|
||||||
|
Repository: re,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Repo = repo
|
||||||
|
|
||||||
|
AddTeamPost(ctx)
|
||||||
|
assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
|
||||||
|
assert.NotEmpty(t, ctx.Flash.ErrorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteTeam(t *testing.T) {
|
||||||
|
models.PrepareTestEnv(t)
|
||||||
|
ctx := test.MockContext(t, "org3/team1/repo3")
|
||||||
|
|
||||||
|
ctx.Req.Form.Set("id", "2")
|
||||||
|
|
||||||
|
org := &models.User{
|
||||||
|
LowerName: "org3",
|
||||||
|
Type: models.UserTypeOrganization,
|
||||||
|
}
|
||||||
|
|
||||||
|
team := &models.Team{
|
||||||
|
ID: 2,
|
||||||
|
OrgID: 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
re := &models.Repository{
|
||||||
|
ID: 3,
|
||||||
|
Owner: org,
|
||||||
|
OwnerID: 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := &context.Repository{
|
||||||
|
Owner: &models.User{
|
||||||
|
ID: 3,
|
||||||
|
LowerName: "org3",
|
||||||
|
RepoAdminChangeTeamAccess: true,
|
||||||
|
},
|
||||||
|
Repository: re,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Repo = repo
|
||||||
|
|
||||||
|
DeleteTeam(ctx)
|
||||||
|
|
||||||
|
assert.False(t, team.HasRepository(re.ID))
|
||||||
|
}
|
||||||
|
|
|
@ -629,6 +629,10 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||||
m.Combo("").Get(repo.Collaboration).Post(repo.CollaborationPost)
|
m.Combo("").Get(repo.Collaboration).Post(repo.CollaborationPost)
|
||||||
m.Post("/access_mode", repo.ChangeCollaborationAccessMode)
|
m.Post("/access_mode", repo.ChangeCollaborationAccessMode)
|
||||||
m.Post("/delete", repo.DeleteCollaboration)
|
m.Post("/delete", repo.DeleteCollaboration)
|
||||||
|
m.Group("/team", func() {
|
||||||
|
m.Post("", repo.AddTeamPost)
|
||||||
|
m.Post("/delete", repo.DeleteTeam)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
m.Group("/branches", func() {
|
m.Group("/branches", func() {
|
||||||
m.Combo("").Get(repo.ProtectedBranch).Post(repo.ProtectedBranchPost)
|
m.Combo("").Get(repo.ProtectedBranch).Post(repo.ProtectedBranchPost)
|
||||||
|
|
|
@ -32,6 +32,17 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="field" id="permission_box">
|
||||||
|
<label>{{.i18n.Tr "org.settings.permission"}}</label>
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input class="hidden" type="checkbox" name="repo_admin_change_team_access" checked/>
|
||||||
|
<label>{{.i18n.Tr "org.settings.repoadminchangeteam"}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="inline field">
|
<div class="inline field">
|
||||||
<label></label>
|
<label></label>
|
||||||
<button class="ui green button">
|
<button class="ui green button">
|
||||||
|
|
|
@ -56,6 +56,16 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="field" id="permission_box">
|
||||||
|
<label>{{.i18n.Tr "org.settings.permission"}}</label>
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input class="hidden" type="checkbox" name="repo_admin_change_team_access" checked/>
|
||||||
|
<label>{{.i18n.Tr "org.settings.repoadminchangeteam"}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{{if .SignedUser.IsAdmin}}
|
{{if .SignedUser.IsAdmin}}
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<div class="ui eight wide column">
|
<div class="ui eight wide column">
|
||||||
<span class="octicon octicon-shield"></span>
|
<span class="octicon octicon-shield"></span>
|
||||||
<div class="ui inline dropdown">
|
<div class="ui inline dropdown">
|
||||||
<div class="text">{{$.i18n.Tr .Collaboration.ModeI18nKey}}</div>
|
<div class="text">{{if eq .Collaboration.Mode 1}}{{$.i18n.Tr "repo.settings.collaboration.read"}}{{else if eq .Collaboration.Mode 2}}{{$.i18n.Tr "repo.settings.collaboration.write"}}{{else if eq .Collaboration.Mode 3}}{{$.i18n.Tr "repo.settings.collaboration.admin"}}{{else}}{{$.i18n.Tr "repo.settings.collaboration.undefined"}}{{end}}</div>
|
||||||
<i class="dropdown icon"></i>
|
<i class="dropdown icon"></i>
|
||||||
<div class="access-mode menu" data-url="{{$.Link}}/access_mode" data-uid="{{.ID}}">
|
<div class="access-mode menu" data-url="{{$.Link}}/access_mode" data-uid="{{.ID}}">
|
||||||
<div class="item" data-text="{{$.i18n.Tr "repo.settings.collaboration.admin"}}" data-value="3">{{$.i18n.Tr "repo.settings.collaboration.admin"}}</div>
|
<div class="item" data-text="{{$.i18n.Tr "repo.settings.collaboration.admin"}}" data-value="3">{{$.i18n.Tr "repo.settings.collaboration.admin"}}</div>
|
||||||
|
@ -51,6 +51,63 @@
|
||||||
<button class="ui green button">{{.i18n.Tr "repo.settings.add_collaborator"}}</button>
|
<button class="ui green button">{{.i18n.Tr "repo.settings.add_collaborator"}}</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h4 class="ui top attached header">
|
||||||
|
Teams
|
||||||
|
</h4>
|
||||||
|
{{ $allowedToChangeTeams := ( or (.Org.RepoAdminChangeTeamAccess) (.Permission.IsOwner)) }}
|
||||||
|
{{if .Teams}}
|
||||||
|
<div class="ui attached segment collaborator list">
|
||||||
|
{{range $t, $team := .Teams}}
|
||||||
|
<div class="item ui grid">
|
||||||
|
<div class="ui five wide column">
|
||||||
|
<a href="{{AppSubUrl}}/org/{{$.OrgName}}/teams/{{.LowerName}}">
|
||||||
|
{{.Name}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="ui eight wide column poping up" data-content="Team's permission is set on the team setting page and can't be changed per repository">
|
||||||
|
<span class="octicon octicon-shield"></span>
|
||||||
|
<div class="ui inline dropdown">
|
||||||
|
<div class="text">{{if eq .Authorize 1}}{{$.i18n.Tr "repo.settings.collaboration.read"}}{{else if eq .Authorize 2}}{{$.i18n.Tr "repo.settings.collaboration.write"}}{{else if eq .Authorize 3}}{{$.i18n.Tr "repo.settings.collaboration.admin"}}{{else if eq .Authorize 4}}{{$.i18n.Tr "repo.settings.collaboration.owner"}}{{else}}{{$.i18n.Tr "repo.settings.collaboration.undefined"}}{{end}}</div>
|
||||||
|
</div>
|
||||||
|
{{ if or (eq .Authorize 1) (eq .Authorize 2) }}
|
||||||
|
{{ $first := true }}
|
||||||
|
<div class="description">
|
||||||
|
Sections: {{range $u, $unit := $.Units}}{{if and ($.Repo.UnitEnabled $unit.Type) ($team.UnitEnabled $unit.Type)}}{{if $first}}{{ $first = false }}{{else}}, {{end}}{{$.i18n.Tr $unit.NameKey}}{{end}}{{end}} {{if $first}}None{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{if $allowedToChangeTeams}}
|
||||||
|
{{ $globalRepoAccess := (eq .LowerName "owners") }}
|
||||||
|
<div class="ui two wide column {{if $globalRepoAccess}}poping up{{end}}" {{if $globalRepoAccess}}data-content="This team has access to all repositories and can't be removed."{{end}}>
|
||||||
|
<button class="ui red tiny button inline text-thin delete-button {{if $globalRepoAccess}}disabled{{end}}" data-url="{{$.Link}}/team/delete" data-id="{{.ID}}">
|
||||||
|
{{$.i18n.Tr "repo.settings.delete_collaborator"}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
<div class="ui bottom attached segment">
|
||||||
|
{{if $allowedToChangeTeams}}
|
||||||
|
<form class="ui form" id="repo-collab-team-form" action="{{.Link}}/team" method="post">
|
||||||
|
{{.CsrfTokenHtml}}
|
||||||
|
<div class="inline field ui left">
|
||||||
|
<div id="search-team-box" class="ui search" data-org="{{.OrgID}}">
|
||||||
|
<div class="ui input">
|
||||||
|
<input class="prompt" name="team" placeholder="Search teams..." autocomplete="off" autofocus required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="ui green button">Add Team</button>
|
||||||
|
</form>
|
||||||
|
{{else}}
|
||||||
|
<div class="item">
|
||||||
|
Changing team access for repository has been restricted to organization owner
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -7718,6 +7718,10 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "Location"
|
"x-go-name": "Location"
|
||||||
},
|
},
|
||||||
|
"repo_admin_change_team_access": {
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "RepoAdminChangeTeamAccess"
|
||||||
|
},
|
||||||
"username": {
|
"username": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "UserName"
|
"x-go-name": "UserName"
|
||||||
|
@ -8262,6 +8266,10 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "Location"
|
"x-go-name": "Location"
|
||||||
},
|
},
|
||||||
|
"repo_admin_change_team_access": {
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "RepoAdminChangeTeamAccess"
|
||||||
|
},
|
||||||
"visibility": {
|
"visibility": {
|
||||||
"description": "possible values are `public`, `limited` or `private`",
|
"description": "possible values are `public`, `limited` or `private`",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
@ -9271,6 +9279,10 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "Location"
|
"x-go-name": "Location"
|
||||||
},
|
},
|
||||||
|
"repo_admin_change_team_access": {
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "RepoAdminChangeTeamAccess"
|
||||||
|
},
|
||||||
"username": {
|
"username": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "UserName"
|
"x-go-name": "UserName"
|
||||||
|
|
Reference in a new issue