mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	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:
		 David Svantesson
					David Svantesson
				
			
				
					committed by
					
						 Lauris BH
						Lauris BH
					
				
			
			
				
	
			
			
			 Lauris BH
						Lauris BH
					
				
			
						parent
						
							63ff61615e
						
					
				
				
					commit
					a0e88dfc2e
				
			| @@ -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 New Issue
	
	Block a user