mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	Finish new edit team page, add member to team
This commit is contained in:
		| @@ -48,7 +48,7 @@ and it takes care of all the other things for you`, | ||||
| 	Flags:  []cli.Flag{}, | ||||
| } | ||||
|  | ||||
| // checkVersion checks if binary matches the version of temolate files. | ||||
| // checkVersion checks if binary matches the version of templates files. | ||||
| func checkVersion() { | ||||
| 	data, err := ioutil.ReadFile(path.Join(setting.StaticRootPath, "templates/.VERSION")) | ||||
| 	if err != nil { | ||||
| @@ -235,7 +235,7 @@ func runWeb(*cli.Context) { | ||||
| 			r.Get("/members/action/:action", org.MembersAction) | ||||
|  | ||||
| 			r.Get("/teams", org.Teams) | ||||
| 			r.Get("/teams/:team", org.SingleTeam) | ||||
| 			r.Get("/teams/:team", org.TeamMembers) | ||||
| 			r.Get("/teams/:team/action/:action", org.TeamsAction) | ||||
| 		}, middleware.OrgAssignment(true, true)) | ||||
|  | ||||
| @@ -243,6 +243,8 @@ func runWeb(*cli.Context) { | ||||
| 			r.Get("/teams/new", org.NewTeam) | ||||
| 			r.Post("/teams/new", bindIgnErr(auth.CreateTeamForm{}), org.NewTeamPost) | ||||
| 			r.Get("/teams/:team/edit", org.EditTeam) | ||||
| 			r.Post("/teams/:team/edit", bindIgnErr(auth.CreateTeamForm{}), org.EditTeamPost) | ||||
| 			r.Post("/teams/:team/delete", org.DeleteTeam) | ||||
|  | ||||
| 			m.Group("/settings", func(r *macaron.Router) { | ||||
| 				r.Get("", org.Settings) | ||||
|   | ||||
| @@ -283,6 +283,13 @@ teams.no_desc = This team has no description | ||||
| teams.settings = Settings | ||||
| teams.owners_permission_desc = Owners have full access to <strong>all repositories</strong> and have <strong>admin rights</strong> to the organization. | ||||
| teams.members = Team Members | ||||
| teams.update_settings = Update Settings | ||||
| teams.delete_team = Delete This Team | ||||
| teams.add_team_member = Add Team Member | ||||
| teams.delete_team_success = Given team has been successfully deleted. | ||||
| teams.read_permission_desc = This team grants <strong>Read</strong> access: members can view and clone the team's repositories. | ||||
| teams.write_permission_desc = This team grants <strong>Write</strong> access: members can read from and push to the team's repositories. | ||||
| teams.admin_permission_desc = This team grants <strong>Admin</strong> access: members can read from, push to, and add collaborators to the team's repositories. | ||||
|  | ||||
| [action] | ||||
| create_repo = created repository <a href="/%s">%s</a> | ||||
|   | ||||
| @@ -283,6 +283,13 @@ teams.no_desc = 该团队暂无描述 | ||||
| teams.settings = 团队设置 | ||||
| teams.owners_permission_desc = 管理员团队对 <strong>所有仓库</strong> 具有操作权限,且对组织具有 <strong>管理员权限</strong>。 | ||||
| teams.members = 团队成员 | ||||
| teams.update_settings = 更新团队设置 | ||||
| teams.delete_team = 删除当前团队 | ||||
| teams.add_team_member = 添加团队成员 | ||||
| teams.delete_team_success = 指定团队已经被成功删除! | ||||
| teams.read_permission_desc = 该团队拥有对所属仓库的 <strong>读取</strong> 权限,团队成员可以进行查看和克隆等只读操作。 | ||||
| teams.write_permission_desc = 该团队拥有对所属仓库的 <strong>读取</strong> 和 <strong>写入</strong> 的权限。 | ||||
| teams.admin_permission_desc = 该团队拥有一定的 <strong>管理</strong> 权限,团队成员可以读取、克隆、推送以及添加其它仓库协作者。 | ||||
|  | ||||
| [action] | ||||
| create_repo = 创建了仓库 <a href="/%s">%s</a> | ||||
|   | ||||
							
								
								
									
										2
									
								
								gogs.go
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								gogs.go
									
									
									
									
									
								
							| @@ -17,7 +17,7 @@ import ( | ||||
| 	"github.com/gogits/gogs/modules/setting" | ||||
| ) | ||||
|  | ||||
| const APP_VER = "0.4.7.0823 Alpha" | ||||
| const APP_VER = "0.4.7.0824 Alpha" | ||||
|  | ||||
| func init() { | ||||
| 	runtime.GOMAXPROCS(runtime.NumCPU()) | ||||
|   | ||||
							
								
								
									
										376
									
								
								models/org.go
									
									
									
									
									
								
							
							
						
						
									
										376
									
								
								models/org.go
									
									
									
									
									
								
							| @@ -6,11 +6,13 @@ package models | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/Unknwon/com" | ||||
| 	"github.com/go-xorm/xorm" | ||||
|  | ||||
| 	"github.com/gogits/gogs/modules/base" | ||||
| ) | ||||
| @@ -134,10 +136,10 @@ func CreateOrganization(org, owner *User) (*User, error) { | ||||
|  | ||||
| 	// Add initial creator to organization and owner team. | ||||
| 	ou := &OrgUser{ | ||||
| 		Uid:     owner.Id, | ||||
| 		OrgId:   org.Id, | ||||
| 		IsOwner: true, | ||||
| 		NumTeam: 1, | ||||
| 		Uid:      owner.Id, | ||||
| 		OrgId:    org.Id, | ||||
| 		IsOwner:  true, | ||||
| 		NumTeams: 1, | ||||
| 	} | ||||
| 	if _, err = sess.Insert(ou); err != nil { | ||||
| 		sess.Rollback() | ||||
| @@ -199,7 +201,7 @@ type OrgUser struct { | ||||
| 	OrgId    int64 `xorm:"INDEX UNIQUE(s)"` | ||||
| 	IsPublic bool | ||||
| 	IsOwner  bool | ||||
| 	NumTeam  int | ||||
| 	NumTeams int | ||||
| } | ||||
|  | ||||
| // IsOrganizationOwner returns true if given user is in the owner team. | ||||
| @@ -255,17 +257,17 @@ func AddOrgUser(orgId, uid int64) error { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	ou := &OrgUser{ | ||||
| 		Uid:   uid, | ||||
| 		OrgId: orgId, | ||||
| 	} | ||||
|  | ||||
| 	sess := x.NewSession() | ||||
| 	defer sess.Close() | ||||
| 	if err := sess.Begin(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	ou := &OrgUser{ | ||||
| 		Uid:   uid, | ||||
| 		OrgId: orgId, | ||||
| 	} | ||||
|  | ||||
| 	if _, err := sess.Insert(ou); err != nil { | ||||
| 		sess.Rollback() | ||||
| 		return err | ||||
| @@ -288,12 +290,17 @@ func RemoveOrgUser(orgId, uid int64) error { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	u, err := GetUserById(uid) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	org, err := GetUserById(orgId) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Check if the user to delete is the last member in owner team. | ||||
| 	if IsOrganizationOwner(orgId, uid) { | ||||
| 		org, err := GetUserById(orgId) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		t, err := org.GetOwnerTeam() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| @@ -317,6 +324,33 @@ func RemoveOrgUser(orgId, uid int64) error { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Delete all repository accesses. | ||||
| 	if err = org.GetRepositories(); err != nil { | ||||
| 		sess.Rollback() | ||||
| 		return err | ||||
| 	} | ||||
| 	access := &Access{ | ||||
| 		UserName: u.LowerName, | ||||
| 	} | ||||
| 	for _, repo := range org.Repos { | ||||
| 		access.RepoName = path.Join(org.LowerName, repo.LowerName) | ||||
| 		if _, err = sess.Delete(access); err != nil { | ||||
| 			sess.Rollback() | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Delete member in his/her teams. | ||||
| 	ts, err := GetUserTeams(org.Id, u.Id) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	for _, t := range ts { | ||||
| 		if err = removeTeamMemberWithSess(org.Id, t.Id, u.Id, sess); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return sess.Commit() | ||||
| } | ||||
|  | ||||
| @@ -352,6 +386,11 @@ type Team struct { | ||||
| 	NumMembers  int | ||||
| } | ||||
|  | ||||
| // IsOwnerTeam returns true if team is owner team. | ||||
| func (t *Team) IsOwnerTeam() bool { | ||||
| 	return t.Name == OWNER_TEAM | ||||
| } | ||||
|  | ||||
| // IsTeamMember returns true if given user is a member of team. | ||||
| func (t *Team) IsMember(uid int64) bool { | ||||
| 	return IsTeamMember(t.OrgId, t.Id, uid) | ||||
| @@ -362,7 +401,10 @@ func (t *Team) GetRepositories() error { | ||||
| 	idStrs := strings.Split(t.RepoIds, "|") | ||||
| 	t.Repos = make([]*Repository, 0, len(idStrs)) | ||||
| 	for _, str := range idStrs { | ||||
| 		id := com.StrTo(str).MustInt64() | ||||
| 		if len(str) == 0 { | ||||
| 			continue | ||||
| 		} | ||||
| 		id := com.StrTo(str[1:]).MustInt64() | ||||
| 		if id == 0 { | ||||
| 			continue | ||||
| 		} | ||||
| @@ -459,15 +501,177 @@ func GetTeamById(teamId int64) (*Team, error) { | ||||
| 	return t, nil | ||||
| } | ||||
|  | ||||
| // GetHighestAuthorize returns highest repository authorize level for given user and team. | ||||
| func GetHighestAuthorize(orgId, uid, teamId, repoId int64) (AuthorizeType, error) { | ||||
| 	ts, err := GetUserTeams(orgId, uid) | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
|  | ||||
| 	var auth AuthorizeType = 0 | ||||
| 	for _, t := range ts { | ||||
| 		// Not current team and has given repository. | ||||
| 		if t.Id != teamId && strings.Contains(t.RepoIds, "$"+com.ToStr(repoId)+"|") { | ||||
| 			// Fast return. | ||||
| 			if t.Authorize == ORG_WRITABLE { | ||||
| 				return ORG_WRITABLE, nil | ||||
| 			} | ||||
| 			if t.Authorize > auth { | ||||
| 				auth = t.Authorize | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return auth, nil | ||||
| } | ||||
|  | ||||
| // UpdateTeam updates information of team. | ||||
| func UpdateTeam(t *Team) error { | ||||
| func UpdateTeam(t *Team, authChanged bool) (err error) { | ||||
| 	if !IsLegalName(t.Name) { | ||||
| 		return ErrTeamNameIllegal | ||||
| 	} | ||||
|  | ||||
| 	if len(t.Description) > 255 { | ||||
| 		t.Description = t.Description[:255] | ||||
| 	} | ||||
|  | ||||
| 	sess := x.NewSession() | ||||
| 	defer sess.Close() | ||||
| 	if err = sess.Begin(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Update access for team members if needed. | ||||
| 	if authChanged && !t.IsOwnerTeam() { | ||||
| 		if err = t.GetRepositories(); err != nil { | ||||
| 			return err | ||||
| 		} else if err = t.GetMembers(); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		// Get organization. | ||||
| 		org, err := GetUserById(t.OrgId) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		mode := READABLE | ||||
| 		if t.Authorize > ORG_READABLE { | ||||
| 			mode = WRITABLE | ||||
| 		} | ||||
| 		access := &Access{ | ||||
| 			Mode: mode, | ||||
| 		} | ||||
|  | ||||
| 		for _, repo := range t.Repos { | ||||
| 			access.RepoName = path.Join(org.LowerName, repo.LowerName) | ||||
| 			for _, u := range t.Members { | ||||
| 				// ORG_WRITABLE is the highest authorize level for now. | ||||
| 				// Skip checking others if current team has this level. | ||||
| 				if t.Authorize < ORG_WRITABLE { | ||||
| 					auth, err := GetHighestAuthorize(org.Id, u.Id, t.Id, repo.Id) | ||||
| 					if err != nil { | ||||
| 						sess.Rollback() | ||||
| 						return err | ||||
| 					} | ||||
| 					if auth >= t.Authorize { | ||||
| 						continue // Other team has higher or same authorize level. | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				access.UserName = u.LowerName | ||||
| 				if _, err = sess.Update(access); err != nil { | ||||
| 					sess.Rollback() | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	t.LowerName = strings.ToLower(t.Name) | ||||
| 	_, err := x.Id(t.Id).AllCols().Update(t) | ||||
| 	return err | ||||
| 	if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil { | ||||
| 		sess.Rollback() | ||||
| 		return err | ||||
| 	} | ||||
| 	return sess.Commit() | ||||
| } | ||||
|  | ||||
| // DeleteTeam deletes given team. | ||||
| // It's caller's responsibility to assign organization ID. | ||||
| func DeleteTeam(t *Team) error { | ||||
| 	if err := t.GetRepositories(); err != nil { | ||||
| 		return err | ||||
| 	} else if err = t.GetMembers(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Get organization. | ||||
| 	org, err := GetUserById(t.OrgId) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	sess := x.NewSession() | ||||
| 	defer sess.Close() | ||||
| 	if err = sess.Begin(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Delete all accesses. | ||||
| 	mode := READABLE | ||||
| 	if t.Authorize > ORG_READABLE { | ||||
| 		mode = WRITABLE | ||||
| 	} | ||||
| 	access := new(Access) | ||||
|  | ||||
| 	for _, repo := range t.Repos { | ||||
| 		access.RepoName = path.Join(org.LowerName, repo.LowerName) | ||||
| 		for _, u := range t.Members { | ||||
| 			access.UserName = u.LowerName | ||||
| 			access.Mode = mode | ||||
| 			auth, err := GetHighestAuthorize(org.Id, u.Id, t.Id, repo.Id) | ||||
| 			if err != nil { | ||||
| 				sess.Rollback() | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			if auth == 0 { | ||||
| 				if _, err = sess.Delete(access); err != nil { | ||||
| 					sess.Rollback() | ||||
| 					return err | ||||
| 				} | ||||
| 			} else if auth < t.Authorize { | ||||
| 				// Downgrade authorize level. | ||||
| 				mode := READABLE | ||||
| 				if auth > ORG_READABLE { | ||||
| 					mode = WRITABLE | ||||
| 				} | ||||
| 				access.Mode = mode | ||||
| 				if _, err = sess.Update(access); err != nil { | ||||
| 					sess.Rollback() | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Delete team-user. | ||||
| 	if _, err = sess.Where("org_id=?", org.Id).Where("team_id=?", t.Id).Delete(new(TeamUser)); err != nil { | ||||
| 		sess.Rollback() | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Delete team. | ||||
| 	if _, err = sess.Id(t.Id).Delete(new(Team)); err != nil { | ||||
| 		sess.Rollback() | ||||
| 		return err | ||||
| 	} | ||||
| 	// Update organization number of teams. | ||||
| 	if _, err = sess.Exec("UPDATE `user` SET num_teams = num_teams - 1 WHERE id = ?", t.OrgId); err != nil { | ||||
| 		sess.Rollback() | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return sess.Commit() | ||||
| } | ||||
|  | ||||
| // ___________                    ____ ___ | ||||
| @@ -509,12 +713,37 @@ func GetTeamMembers(orgId, teamId int64) ([]*User, error) { | ||||
| 	return us, nil | ||||
| } | ||||
|  | ||||
| // GetUserTeams returns all teams that user belongs to in given origanization. | ||||
| func GetUserTeams(orgId, uid int64) ([]*Team, error) { | ||||
| 	tus := make([]*TeamUser, 0, 5) | ||||
| 	if err := x.Where("uid=?", uid).And("org_id=?", orgId).Find(&tus); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	ts := make([]*Team, len(tus)) | ||||
| 	for i, tu := range tus { | ||||
| 		t := new(Team) | ||||
| 		has, err := x.Id(tu.TeamId).Get(t) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} else if !has { | ||||
| 			return nil, ErrTeamNotExist | ||||
| 		} | ||||
| 		ts[i] = t | ||||
| 	} | ||||
| 	return ts, nil | ||||
| } | ||||
|  | ||||
| // AddTeamMember adds new member to given team of given organization. | ||||
| func AddTeamMember(orgId, teamId, uid int64) error { | ||||
| 	if !IsOrganizationMember(orgId, uid) || IsTeamMember(orgId, teamId, uid) { | ||||
| 	if IsTeamMember(orgId, teamId, uid) { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	if err := AddOrgUser(orgId, uid); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Get team and its repositories. | ||||
| 	t, err := GetTeamById(teamId) | ||||
| 	if err != nil { | ||||
| @@ -569,18 +798,49 @@ func AddTeamMember(orgId, teamId, uid int64) error { | ||||
|  | ||||
| 	// Give access to team repositories. | ||||
| 	for _, repo := range t.Repos { | ||||
| 		access.RepoName = path.Join(org.LowerName, repo.LowerName) | ||||
| 		if _, err = sess.Insert(access); err != nil { | ||||
| 		auth, err := GetHighestAuthorize(orgId, uid, teamId, repo.Id) | ||||
| 		if err != nil { | ||||
| 			sess.Rollback() | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		access.Id = 0 | ||||
| 		access.RepoName = path.Join(org.LowerName, repo.LowerName) | ||||
| 		// Equal 0 means given access doesn't exist. | ||||
| 		if auth == 0 { | ||||
| 			if _, err = sess.Insert(access); err != nil { | ||||
| 				sess.Rollback() | ||||
| 				return err | ||||
| 			} | ||||
| 		} else if auth < t.Authorize { | ||||
| 			if _, err = sess.Update(access); err != nil { | ||||
| 				sess.Rollback() | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	fmt.Println("kao") | ||||
|  | ||||
| 	// We make sure it exists before. | ||||
| 	ou := new(OrgUser) | ||||
| 	_, err = sess.Where("uid=?", uid).And("org_id=?", orgId).Get(ou) | ||||
| 	if err != nil { | ||||
| 		sess.Rollback() | ||||
| 		return err | ||||
| 	} | ||||
| 	ou.NumTeams++ | ||||
| 	if t.IsOwnerTeam() { | ||||
| 		ou.IsOwner = true | ||||
| 	} | ||||
| 	if _, err = sess.Id(ou.Id).AllCols().Update(ou); err != nil { | ||||
| 		sess.Rollback() | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return sess.Commit() | ||||
| } | ||||
|  | ||||
| // RemoveTeamMember removes member from given team of given organization. | ||||
| func RemoveTeamMember(orgId, teamId, uid int64) error { | ||||
| func removeTeamMemberWithSess(orgId, teamId, uid int64, sess *xorm.Session) error { | ||||
| 	if !IsTeamMember(orgId, teamId, uid) { | ||||
| 		return nil | ||||
| 	} | ||||
| @@ -590,6 +850,12 @@ func RemoveTeamMember(orgId, teamId, uid int64) error { | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Check if the user to delete is the last member in owner team. | ||||
| 	if t.IsOwnerTeam() && t.NumMembers == 1 { | ||||
| 		return ErrLastOrgOwner | ||||
| 	} | ||||
|  | ||||
| 	t.NumMembers-- | ||||
|  | ||||
| 	if err = t.GetRepositories(); err != nil { | ||||
| @@ -608,22 +874,12 @@ func RemoveTeamMember(orgId, teamId, uid int64) error { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	sess := x.NewSession() | ||||
| 	defer sess.Close() | ||||
| 	if err := sess.Begin(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	tu := &TeamUser{ | ||||
| 		Uid:    uid, | ||||
| 		OrgId:  orgId, | ||||
| 		TeamId: teamId, | ||||
| 	} | ||||
|  | ||||
| 	access := &Access{ | ||||
| 		UserName: u.LowerName, | ||||
| 	} | ||||
|  | ||||
| 	if _, err := sess.Delete(tu); err != nil { | ||||
| 		sess.Rollback() | ||||
| 		return err | ||||
| @@ -633,13 +889,63 @@ func RemoveTeamMember(orgId, teamId, uid int64) error { | ||||
| 	} | ||||
|  | ||||
| 	// Delete access to team repositories. | ||||
| 	access := &Access{ | ||||
| 		UserName: u.LowerName, | ||||
| 	} | ||||
|  | ||||
| 	for _, repo := range t.Repos { | ||||
| 		access.RepoName = path.Join(org.LowerName, repo.LowerName) | ||||
| 		if _, err = sess.Delete(access); err != nil { | ||||
| 		auth, err := GetHighestAuthorize(orgId, uid, teamId, repo.Id) | ||||
| 		if err != nil { | ||||
| 			sess.Rollback() | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		// Delete access if this is the last team user belongs to. | ||||
| 		if auth == 0 { | ||||
| 			access.RepoName = path.Join(org.LowerName, repo.LowerName) | ||||
| 			_, err = sess.Delete(access) | ||||
| 		} else if auth < t.Authorize { | ||||
| 			// Downgrade authorize level. | ||||
| 			mode := READABLE | ||||
| 			if auth > ORG_READABLE { | ||||
| 				mode = WRITABLE | ||||
| 			} | ||||
| 			access.Mode = mode | ||||
| 			_, err = sess.Update(access) | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			sess.Rollback() | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// This must exist. | ||||
| 	ou := new(OrgUser) | ||||
| 	_, err = sess.Where("uid=?", uid).And("org_id=?", org.Id).Get(ou) | ||||
| 	if err != nil { | ||||
| 		sess.Rollback() | ||||
| 		return err | ||||
| 	} | ||||
| 	ou.NumTeams-- | ||||
| 	if t.IsOwnerTeam() { | ||||
| 		ou.IsOwner = false | ||||
| 	} | ||||
| 	if _, err = sess.Id(ou.Id).AllCols().Update(ou); err != nil { | ||||
| 		sess.Rollback() | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // RemoveTeamMember removes member from given team of given organization. | ||||
| func RemoveTeamMember(orgId, teamId, uid int64) error { | ||||
| 	sess := x.NewSession() | ||||
| 	defer sess.Close() | ||||
| 	if err := sess.Begin(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := removeTeamMemberWithSess(orgId, teamId, uid, sess); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return sess.Commit() | ||||
| } | ||||
|   | ||||
| @@ -525,6 +525,7 @@ func CreateRepository(u *User, name, desc, lang, license string, private, mirror | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		for _, u := range us { | ||||
| 			access.Id = 0 | ||||
| 			access.UserName = u.LowerName | ||||
| 			if _, err = sess.Insert(access); err != nil { | ||||
| 				sess.Rollback() | ||||
| @@ -707,6 +708,10 @@ func TransferOwnership(u *User, newOwner string, repo *Repository) (err error) { | ||||
|  | ||||
| // ChangeRepositoryName changes all corresponding setting from old repository name to new one. | ||||
| func ChangeRepositoryName(userName, oldRepoName, newRepoName string) (err error) { | ||||
| 	if !IsLegalName(newRepoName) { | ||||
| 		return ErrRepoNameIllegal | ||||
| 	} | ||||
|  | ||||
| 	// Update accesses. | ||||
| 	accesses := make([]Access, 0, 10) | ||||
| 	if err = x.Find(&accesses, &Access{RepoName: strings.ToLower(userName + "/" + oldRepoName)}); err != nil { | ||||
|   | ||||
| @@ -54,7 +54,8 @@ type User struct { | ||||
| 	LoginSource   int64 `xorm:"not null default 0"` | ||||
| 	LoginName     string | ||||
| 	Type          UserType | ||||
| 	Orgs          []*User `xorm:"-"` | ||||
| 	Orgs          []*User       `xorm:"-"` | ||||
| 	Repos         []*Repository `xorm:"-"` | ||||
| 	NumFollowers  int | ||||
| 	NumFollowings int | ||||
| 	NumStars      int | ||||
| @@ -143,6 +144,12 @@ func (u *User) GetOrganizationCount() (int64, error) { | ||||
| 	return x.Where("uid=?", u.Id).Count(new(OrgUser)) | ||||
| } | ||||
|  | ||||
| // GetRepositories returns all repositories that user owns, including private repositories. | ||||
| func (u *User) GetRepositories() (err error) { | ||||
| 	u.Repos, err = GetRepositories(u.Id, true) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // GetOrganizations returns all organizations that user belongs to. | ||||
| func (u *User) GetOrganizations() error { | ||||
| 	ous, err := GetOrgUsersByUserId(u.Id) | ||||
|   | ||||
| @@ -46,6 +46,7 @@ type Context struct { | ||||
| 		IsBranch    bool | ||||
| 		IsTag       bool | ||||
| 		IsCommit    bool | ||||
| 		IsAdmin     bool // Current user is admin level. | ||||
| 		HasAccess   bool | ||||
| 		Repository  *models.Repository | ||||
| 		Owner       *models.User | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import ( | ||||
| 	"github.com/Unknwon/macaron" | ||||
|  | ||||
| 	"github.com/gogits/gogs/models" | ||||
| 	"github.com/gogits/gogs/modules/log" | ||||
| ) | ||||
|  | ||||
| func OrgAssignment(redirect bool, args ...bool) macaron.Handler { | ||||
| @@ -35,6 +36,7 @@ func OrgAssignment(redirect bool, args ...bool) macaron.Handler { | ||||
| 			if err == models.ErrUserNotExist { | ||||
| 				ctx.Handle(404, "GetUserByName", err) | ||||
| 			} else if redirect { | ||||
| 				log.Error(4, "GetUserByName", err) | ||||
| 				ctx.Redirect("/") | ||||
| 			} else { | ||||
| 				ctx.Handle(500, "GetUserByName", err) | ||||
| @@ -52,17 +54,14 @@ func OrgAssignment(redirect bool, args ...bool) macaron.Handler { | ||||
| 			} else { | ||||
| 				if org.IsOrgMember(ctx.User.Id) { | ||||
| 					ctx.Org.IsMember = true | ||||
| 					// TODO: ctx.Org.IsAdminTeam | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		if (requireMember && !ctx.Org.IsMember) || | ||||
| 			(requireOwner && !ctx.Org.IsOwner) || | ||||
| 			(requireAdminTeam && !ctx.Org.IsAdminTeam) { | ||||
| 			(requireOwner && !ctx.Org.IsOwner) { | ||||
| 			ctx.Handle(404, "OrgAssignment", err) | ||||
| 			return | ||||
| 		} | ||||
| 		ctx.Data["IsAdminTeam"] = ctx.Org.IsAdminTeam | ||||
| 		ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner | ||||
|  | ||||
| 		ctx.Org.OrgLink = "/org/" + org.Name | ||||
| @@ -76,6 +75,7 @@ func OrgAssignment(redirect bool, args ...bool) macaron.Handler { | ||||
| 				if err == models.ErrTeamNotExist { | ||||
| 					ctx.Handle(404, "GetTeam", err) | ||||
| 				} else if redirect { | ||||
| 					log.Error(4, "GetTeam", err) | ||||
| 					ctx.Redirect("/") | ||||
| 				} else { | ||||
| 					ctx.Handle(500, "GetTeam", err) | ||||
| @@ -83,6 +83,12 @@ func OrgAssignment(redirect bool, args ...bool) macaron.Handler { | ||||
| 				return | ||||
| 			} | ||||
| 			ctx.Data["Team"] = ctx.Org.Team | ||||
| 			ctx.Org.IsAdminTeam = ctx.Org.Team.IsOwnerTeam() || ctx.Org.Team.Authorize == models.ORG_ADMIN | ||||
| 		} | ||||
| 		ctx.Data["IsAdminTeam"] = ctx.Org.IsAdminTeam | ||||
| 		if requireAdminTeam && !ctx.Org.IsAdminTeam { | ||||
| 			ctx.Handle(404, "OrgAssignment", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -59,6 +59,7 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler { | ||||
| 				if err == models.ErrUserNotExist { | ||||
| 					ctx.Handle(404, "GetUserByName", err) | ||||
| 				} else if redirect { | ||||
| 					log.Error(4, "GetUserByName", err) | ||||
| 					ctx.Redirect("/") | ||||
| 				} else { | ||||
| 					ctx.Handle(500, "GetUserByName", err) | ||||
| @@ -84,7 +85,7 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler { | ||||
| 			ctx.Repo.IsTrueOwner = true | ||||
| 		} | ||||
|  | ||||
| 		// get repository | ||||
| 		// Get repository. | ||||
| 		repo, err := models.GetRepositoryByName(u.Id, repoName) | ||||
| 		if err != nil { | ||||
| 			if err == models.ErrRepoNotExist { | ||||
| @@ -102,8 +103,22 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler { | ||||
| 		} | ||||
|  | ||||
| 		// Check if the mirror repository owner(mirror repository doesn't have access). | ||||
| 		if ctx.IsSigned && !ctx.Repo.IsOwner && repo.OwnerId == ctx.User.Id { | ||||
| 			ctx.Repo.IsOwner = true | ||||
| 		if ctx.IsSigned && !ctx.Repo.IsOwner { | ||||
| 			if repo.OwnerId == ctx.User.Id { | ||||
| 				ctx.Repo.IsOwner = true | ||||
| 			} | ||||
| 			// Check if current user has admin permission to repository. | ||||
| 			if u.IsOrganization() { | ||||
| 				auth, err := models.GetHighestAuthorize(u.Id, ctx.User.Id, 0, repo.Id) | ||||
| 				if err != nil { | ||||
| 					ctx.Handle(500, "GetHighestAuthorize", err) | ||||
| 					return | ||||
| 				} | ||||
| 				if auth == models.ORG_ADMIN { | ||||
| 					ctx.Repo.IsOwner = true | ||||
| 					ctx.Repo.IsAdmin = true | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Check access. | ||||
| @@ -281,7 +296,7 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler { | ||||
|  | ||||
| func RequireTrueOwner() macaron.Handler { | ||||
| 	return func(ctx *Context) { | ||||
| 		if !ctx.Repo.IsTrueOwner { | ||||
| 		if !ctx.Repo.IsTrueOwner && !ctx.Repo.IsAdmin { | ||||
| 			if !ctx.IsSigned { | ||||
| 				ctx.SetCookie("redirect_to", "/"+url.QueryEscape(ctx.Req.RequestURI)) | ||||
| 				ctx.Redirect("/user/login") | ||||
|   | ||||
| @@ -1298,27 +1298,33 @@ The register and sign-in page style | ||||
| .repo-setting-zone { | ||||
|   padding: 30px; | ||||
| } | ||||
| #team-members-list, | ||||
| #repo-collab-list { | ||||
|   list-style: none; | ||||
|   padding: 10px 0 5px 0; | ||||
| } | ||||
| #team-members-list li.collab, | ||||
| #repo-collab-list li.collab { | ||||
|   clear: both; | ||||
|   height: 50px; | ||||
|   padding: 0 15px 0 15px; | ||||
| } | ||||
| #team-members-list a.member, | ||||
| #repo-collab-list a.member { | ||||
|   color: #444; | ||||
|   height: 50px; | ||||
|   line-height: 50px; | ||||
| } | ||||
| #team-members-list a.member:hover, | ||||
| #repo-collab-list a.member:hover { | ||||
|   color: #4183C4; | ||||
| } | ||||
| #team-members-list .avatar, | ||||
| #repo-collab-list .avatar { | ||||
|   margin-right: 1em; | ||||
|   width: 40px; | ||||
| } | ||||
| #team-members-list .remove-collab, | ||||
| #repo-collab-list .remove-collab { | ||||
|   color: #DD4B39; | ||||
| } | ||||
| @@ -1871,3 +1877,14 @@ textarea#issue-add-content { | ||||
| #org-team-card .panel-footer { | ||||
|   padding: 10px 20px; | ||||
| } | ||||
| #team-members-list .panel-body .search { | ||||
|   padding: 4px 0 10px 10px; | ||||
|   border-bottom: 1px solid #dddddd; | ||||
| } | ||||
| #team-members-list li.collab { | ||||
|   padding-top: 10px !important; | ||||
|   border-bottom: 1px solid #dddddd; | ||||
| } | ||||
| #team-members-list li.collab:last-child { | ||||
|   border-bottom: 0; | ||||
| } | ||||
|   | ||||
| @@ -351,6 +351,41 @@ function initInvite() { | ||||
|     }); | ||||
| } | ||||
|  | ||||
| function initOrgTeamCreate() { | ||||
|     // Delete team. | ||||
|     $('#org-team-delete').click(function (e) { | ||||
|         if (!confirm('This team is going to be deleted, do you want to continue?')) { | ||||
|             e.preventDefault(); | ||||
|             return true; | ||||
|         } | ||||
|         var $form = $('#team-create-form') | ||||
|         $form.attr('action', $form.data('delete-url')); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| function initTeamMembersList() { | ||||
|     // Add team member. | ||||
|     var $ul = $('#org-team-members-list'); | ||||
|     $('#org-team-members-add').on('keyup', function () { | ||||
|         var $this = $(this); | ||||
|         if (!$this.val()) { | ||||
|             $ul.toggleHide(); | ||||
|             return; | ||||
|         } | ||||
|         Gogs.searchUsers($this.val(), $ul); | ||||
|     }).on('focus', function () { | ||||
|         if (!$(this).val()) { | ||||
|             $ul.toggleHide(); | ||||
|         } else { | ||||
|             $ul.toggleShow(); | ||||
|         } | ||||
|     }).next().next().find('ul').on("click", 'li', function () { | ||||
|         $('#org-team-members-add').val($(this).text()); | ||||
|         $ul.toggleHide(); | ||||
|     }); | ||||
|  | ||||
| } | ||||
|  | ||||
| $(document).ready(function () { | ||||
|     initCore(); | ||||
|     if ($('#user-profile-setting').length) { | ||||
| @@ -368,6 +403,12 @@ $(document).ready(function () { | ||||
|     if ($('#invite-box').length) { | ||||
|         initInvite(); | ||||
|     } | ||||
|     if ($('#team-create-form').length) { | ||||
|         initOrgTeamCreate(); | ||||
|     } | ||||
|     if ($('#team-members-list').length) { | ||||
|         initTeamMembersList(); | ||||
|     } | ||||
|  | ||||
|     Tabs('#dashboard-sidebar-menu'); | ||||
|  | ||||
|   | ||||
| @@ -197,3 +197,18 @@ | ||||
| 		padding: 10px 20px; | ||||
| 	} | ||||
| } | ||||
| #team-members-list { | ||||
| 	.panel-body .search { | ||||
| 		padding: 4px 0 10px 10px; | ||||
| 		border-bottom: 1px solid #dddddd; | ||||
| 	} | ||||
| } | ||||
| #team-members-list { | ||||
| 	li.collab { | ||||
| 		padding-top: 10px !important; | ||||
| 		border-bottom: 1px solid #dddddd; | ||||
| 		&:last-child { | ||||
| 			border-bottom: 0; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -426,6 +426,7 @@ border-top-right-radius: .25em; | ||||
| .repo-setting-zone { | ||||
| 	padding: 30px; | ||||
| } | ||||
| #team-members-list, | ||||
| #repo-collab-list { | ||||
| 	list-style: none; | ||||
| 	padding: 10px 0 5px 0; | ||||
|   | ||||
| @@ -82,7 +82,12 @@ func MembersAction(ctx *middleware.Context) { | ||||
| 		}) | ||||
| 		return | ||||
| 	} | ||||
| 	ctx.Redirect(ctx.Org.OrgLink + "/members") | ||||
|  | ||||
| 	if ctx.Params(":action") != "leave" { | ||||
| 		ctx.Redirect(ctx.Org.OrgLink + "/members") | ||||
| 	} else { | ||||
| 		ctx.Redirect("/") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func Invitation(ctx *middleware.Context) { | ||||
|   | ||||
| @@ -5,6 +5,8 @@ | ||||
| package org | ||||
|  | ||||
| import ( | ||||
| 	"github.com/Unknwon/com" | ||||
|  | ||||
| 	"github.com/gogits/gogs/models" | ||||
| 	"github.com/gogits/gogs/modules/auth" | ||||
| 	"github.com/gogits/gogs/modules/base" | ||||
| @@ -39,23 +41,71 @@ func Teams(ctx *middleware.Context) { | ||||
| } | ||||
|  | ||||
| func TeamsAction(ctx *middleware.Context) { | ||||
| 	uid := com.StrTo(ctx.Query("uid")).MustInt64() | ||||
| 	if uid == 0 { | ||||
| 		ctx.Redirect(ctx.Org.OrgLink + "/teams") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	page := ctx.Query("page") | ||||
| 	var err error | ||||
| 	switch ctx.Params(":action") { | ||||
| 	case "join": | ||||
| 		if !ctx.Org.IsOwner { | ||||
| 			ctx.Error(404) | ||||
| 			return | ||||
| 		} | ||||
| 		err = ctx.Org.Team.AddMember(ctx.User.Id) | ||||
| 	case "leave": | ||||
| 		err = ctx.Org.Team.RemoveMember(ctx.User.Id) | ||||
| 	case "remove": | ||||
| 		if !ctx.Org.IsOwner { | ||||
| 			ctx.Error(404) | ||||
| 			return | ||||
| 		} | ||||
| 		err = ctx.Org.Team.RemoveMember(uid) | ||||
| 		page = "team" | ||||
| 	case "add": | ||||
| 		if !ctx.Org.IsOwner { | ||||
| 			ctx.Error(404) | ||||
| 			return | ||||
| 		} | ||||
| 		uname := ctx.Query("uname") | ||||
| 		var u *models.User | ||||
| 		u, err = models.GetUserByName(uname) | ||||
| 		if err != nil { | ||||
| 			if err == models.ErrUserNotExist { | ||||
| 				ctx.Flash.Error(ctx.Tr("form.user_not_exist")) | ||||
| 				ctx.Redirect(ctx.Org.OrgLink + "/teams/" + ctx.Org.Team.LowerName) | ||||
| 			} else { | ||||
| 				ctx.Handle(500, " GetUserByName", err) | ||||
| 			} | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		err = ctx.Org.Team.AddMember(u.Id) | ||||
| 		page = "team" | ||||
| 	} | ||||
|  | ||||
| 	if err != nil { | ||||
| 		log.Error(4, "Action(%s): %v", ctx.Params(":action"), err) | ||||
| 		ctx.JSON(200, map[string]interface{}{ | ||||
| 			"ok":  false, | ||||
| 			"err": err.Error(), | ||||
| 		}) | ||||
| 		return | ||||
| 		if err == models.ErrLastOrgOwner { | ||||
| 			ctx.Flash.Error(ctx.Tr("form.last_org_owner")) | ||||
| 		} else { | ||||
| 			log.Error(4, "Action(%s): %v", ctx.Params(":action"), err) | ||||
| 			ctx.JSON(200, map[string]interface{}{ | ||||
| 				"ok":  false, | ||||
| 				"err": err.Error(), | ||||
| 			}) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	switch page { | ||||
| 	case "team": | ||||
| 		ctx.Redirect(ctx.Org.OrgLink + "/teams/" + ctx.Org.Team.LowerName) | ||||
| 	default: | ||||
| 		ctx.Redirect(ctx.Org.OrgLink + "/teams") | ||||
| 	} | ||||
| 	ctx.Redirect(ctx.Org.OrgLink + "/teams") | ||||
| } | ||||
|  | ||||
| func NewTeam(ctx *middleware.Context) { | ||||
| @@ -116,13 +166,76 @@ func NewTeamPost(ctx *middleware.Context, form auth.CreateTeamForm) { | ||||
| 	ctx.Redirect(ctx.Org.OrgLink + "/teams/" + t.LowerName) | ||||
| } | ||||
|  | ||||
| func EditTeam(ctx *middleware.Context) { | ||||
| 	ctx.Data["Title"] = "Organization " + ctx.Params(":org") + " Edit Team" | ||||
| 	ctx.HTML(200, "org/edit_team") | ||||
| } | ||||
|  | ||||
| func SingleTeam(ctx *middleware.Context) { | ||||
| func TeamMembers(ctx *middleware.Context) { | ||||
| 	ctx.Data["Title"] = ctx.Org.Team.Name | ||||
| 	ctx.Data["PageIsOrgTeams"] = true | ||||
| 	if err := ctx.Org.Team.GetMembers(); err != nil { | ||||
| 		ctx.Handle(500, "GetMembers", err) | ||||
| 		return | ||||
| 	} | ||||
| 	ctx.HTML(200, TEAM_MEMBERS) | ||||
| } | ||||
|  | ||||
| func EditTeam(ctx *middleware.Context) { | ||||
| 	ctx.Data["Title"] = ctx.Org.Organization.FullName | ||||
| 	ctx.Data["PageIsOrgTeams"] = true | ||||
| 	ctx.Data["team_name"] = ctx.Org.Team.Name | ||||
| 	ctx.Data["desc"] = ctx.Org.Team.Description | ||||
| 	ctx.HTML(200, TEAM_NEW) | ||||
| } | ||||
|  | ||||
| func EditTeamPost(ctx *middleware.Context, form auth.CreateTeamForm) { | ||||
| 	t := ctx.Org.Team | ||||
| 	ctx.Data["Title"] = ctx.Org.Organization.FullName | ||||
| 	ctx.Data["PageIsOrgTeams"] = true | ||||
| 	ctx.Data["team_name"] = t.Name | ||||
| 	ctx.Data["desc"] = t.Description | ||||
|  | ||||
| 	if ctx.HasError() { | ||||
| 		ctx.HTML(200, TEAM_NEW) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	isAuthChanged := false | ||||
| 	if !t.IsOwnerTeam() { | ||||
| 		// Validate permission level. | ||||
| 		var auth models.AuthorizeType | ||||
| 		switch form.Permission { | ||||
| 		case "read": | ||||
| 			auth = models.ORG_READABLE | ||||
| 		case "write": | ||||
| 			auth = models.ORG_WRITABLE | ||||
| 		case "admin": | ||||
| 			auth = models.ORG_ADMIN | ||||
| 		default: | ||||
| 			ctx.Error(401) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		t.Name = form.TeamName | ||||
| 		if t.Authorize != auth { | ||||
| 			isAuthChanged = true | ||||
| 			t.Authorize = auth | ||||
| 		} | ||||
| 	} | ||||
| 	t.Description = form.Description | ||||
| 	if err := models.UpdateTeam(t, isAuthChanged); err != nil { | ||||
| 		if err == models.ErrTeamNameIllegal { | ||||
| 			ctx.Data["Err_TeamName"] = true | ||||
| 			ctx.RenderWithErr(ctx.Tr("form.illegal_team_name"), TEAM_NEW, &form) | ||||
| 		} else { | ||||
| 			ctx.Handle(500, "UpdateTeam", err) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 	ctx.Redirect(ctx.Org.OrgLink + "/teams/" + t.LowerName) | ||||
| } | ||||
|  | ||||
| func DeleteTeam(ctx *middleware.Context) { | ||||
| 	if err := models.DeleteTeam(ctx.Org.Team); err != nil { | ||||
| 		ctx.Handle(500, "DeleteTeam", err) | ||||
| 		return | ||||
| 	} | ||||
| 	ctx.Flash.Success(ctx.Tr("org.teams.delete_team_success")) | ||||
| 	ctx.Redirect(ctx.Org.OrgLink + "/teams") | ||||
| } | ||||
|   | ||||
| @@ -56,7 +56,12 @@ func SettingsPost(ctx *middleware.Context, form auth.RepoSettingForm) { | ||||
| 				ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), SETTINGS_OPTIONS, nil) | ||||
| 				return | ||||
| 			} else if err = models.ChangeRepositoryName(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name, newRepoName); err != nil { | ||||
| 				ctx.Handle(500, "ChangeRepositoryName", err) | ||||
| 				if err == models.ErrRepoNameIllegal { | ||||
| 					ctx.Data["Err_RepoName"] = true | ||||
| 					ctx.RenderWithErr(ctx.Tr("form.illegal_repo_name"), SETTINGS_OPTIONS, nil) | ||||
| 				} else { | ||||
| 					ctx.Handle(500, "ChangeRepositoryName", err) | ||||
| 				} | ||||
| 				return | ||||
| 			} | ||||
| 			log.Trace("Repository name changed: %s/%s -> %s", ctx.Repo.Owner.Name, ctx.Repo.Repository.Name, newRepoName) | ||||
| @@ -185,9 +190,24 @@ func SettingsCollaboration(ctx *middleware.Context) { | ||||
| 	// Delete collaborator. | ||||
| 	remove := strings.ToLower(ctx.Query("remove")) | ||||
| 	if len(remove) > 0 && remove != ctx.Repo.Owner.LowerName { | ||||
| 		if err := models.DeleteAccess(&models.Access{UserName: remove, RepoName: repoLink}); err != nil { | ||||
| 			ctx.Handle(500, "DeleteAccess", err) | ||||
| 			return | ||||
| 		needDelete := true | ||||
| 		if ctx.User.IsOrganization() { | ||||
| 			// Check if user belongs to a team that has access to this repository. | ||||
| 			auth, err := models.GetHighestAuthorize(ctx.Repo.Owner.Id, ctx.User.Id, 0, ctx.Repo.Repository.Id) | ||||
| 			if err != nil { | ||||
| 				ctx.Handle(500, "GetHighestAuthorize", err) | ||||
| 				return | ||||
| 			} | ||||
| 			if auth > 0 { | ||||
| 				needDelete = false | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if needDelete { | ||||
| 			if err := models.DeleteAccess(&models.Access{UserName: remove, RepoName: repoLink}); err != nil { | ||||
| 				ctx.Handle(500, "DeleteAccess", err) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 		ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success")) | ||||
| 		ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| 0.4.7.0823 Alpha | ||||
| 0.4.7.0824 Alpha | ||||
| @@ -18,6 +18,7 @@ | ||||
| 	</div> | ||||
| </div> | ||||
| <div class="container"> | ||||
| 	{{$isMember := .Org.IsOrgMember $.SignedUser.Id}} | ||||
|     <div id="org-home-repo-list" class="left grid-2-3"> | ||||
|         <div class="clear"> | ||||
|         	{{if .IsOrganizationOwner}} | ||||
| @@ -26,6 +27,7 @@ | ||||
|         </div> | ||||
|         <div id="org-repo-list"> | ||||
| 			{{range .Repos}} | ||||
| 				{{if or $isMember (not .IsPrivate)}} | ||||
| 				<div class="org-repo-item"> | ||||
|                     <ul class="org-repo-status right"> | ||||
|                         <li><i class="octicon octicon-star"></i> {{.NumStars}}</li> | ||||
| @@ -35,6 +37,7 @@ | ||||
| 					<p class="org-repo-description">{{.Description}}</p> | ||||
| 					<p class="org-repo-updated">{{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Updated $.i18n.Lang}}</p> | ||||
| 				</div> | ||||
| 				{{end}} | ||||
| 			{{end}} | ||||
|         </div> | ||||
|     </div> | ||||
| @@ -42,12 +45,16 @@ | ||||
|     	<div class="org-sidebar"> | ||||
| 	    	<div class="panel panel-radius"> | ||||
| 	    		<div class="panel-header"> | ||||
| 	    			{{if $isMember}} | ||||
| 	    			<a class="text-grey right" href="/org/{{.Org.LowerName}}/members"><strong>{{.Org.NumMembers}}</strong><span class="octicon octicon-chevron-right"></span></a> | ||||
| 	    			{{end}} | ||||
| 	    			<strong>{{.i18n.Tr "org.people"}}</strong> | ||||
| 	    		</div> | ||||
| 	    		<div class="panel-body member-avatar-group"> | ||||
| 	    			{{range .Members}} | ||||
| 	    			<a href="/{{.Name}}" title="{{.Name}}"><img src="{{.AvatarLink}}"></a> | ||||
| 						{{if or $isMember (.IsPublicMember $.Org.Id)}} | ||||
| 	    				<a href="/{{.Name}}" title="{{.Name}}"><img src="{{.AvatarLink}}"></a> | ||||
| 	    				{{end}} | ||||
| 	    			{{end}} | ||||
| 	    		</div> | ||||
| 	    		{{if .IsOrganizationOwner}} | ||||
| @@ -56,6 +63,7 @@ | ||||
| 	    		</div> | ||||
| 	    		{{end}} | ||||
| 	    	</div> | ||||
| 	    	{{if $isMember}} | ||||
| 	    	<br> | ||||
| 	    	<div class="panel panel-radius"> | ||||
| 	    		<div class="panel-header"> | ||||
| @@ -76,9 +84,9 @@ | ||||
| 	    		<div class="panel-footer"> | ||||
| 	    			<a class="btn btn-medium btn-blue btn-link btn-radius" href="/org/{{$.Org.LowerName}}/teams/new">{{.i18n.Tr "org.create_new_team"}}</a> | ||||
| 	    		</div> | ||||
|  | ||||
| 	    		{{end}} | ||||
| 	    	</div> | ||||
| 	    	{{end}} | ||||
|     	</div> | ||||
|     </div> | ||||
| </div> | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
| 		{{template "ng/base/alert" .}} | ||||
| 	</div> | ||||
| 	<div class="org-toolbar clear"> | ||||
| 		{{if .IsAdminTeam}} | ||||
| 		{{if .IsOrganizationOwner}} | ||||
|         <a class="btn btn-green btn-large btn-link btn-radius right" href="{{.OrgLink}}/invitations/new"><i class="octicon octicon-repo-create"></i> {{.i18n.Tr "org.invite_someone"}}</a> | ||||
| 		{{end}} | ||||
| 	</div> | ||||
|   | ||||
| @@ -2,7 +2,8 @@ | ||||
| {{template "ng/base/header" .}} | ||||
| {{template "org/base/header" .}} | ||||
| <div id="setting-wrapper" class="main-wrapper"> | ||||
|     <div id="org-setting" class="container clear"> | ||||
|     <div id="team-members-list" class="container clear"> | ||||
| 		{{template "ng/base/alert" .}} | ||||
| 		{{template "org/team/sidebar" .}} | ||||
| 		<div class="grid-2-3 left"> | ||||
| 			<div class="setting-content"> | ||||
| @@ -10,6 +11,32 @@ | ||||
| 					<div class="panel-header"> | ||||
| 						{{.i18n.Tr "org.teams.members"}} | ||||
| 					</div> | ||||
|                     <ul class="panel-body setting-list" id="team-members-list"> | ||||
|                     	{{if .IsOrganizationOwner}} | ||||
| 						<li class="search"> | ||||
| 			                <form class="form form-align" action="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/add" id="repo-collab-form"> | ||||
| 			                    {{.CsrfTokenHtml}} | ||||
| 			                    <input type="hidden" name="uid" value="{{.SignedUser.Id}}"> | ||||
|                                 <input class="ipt ipt-large ipt-radius" id="org-team-members-add" name="uname" autocomplete="off" required /> | ||||
|                                 <button class="btn btn-blue btn-large btn-radius">{{.i18n.Tr "org.teams.add_team_member"}}</button> | ||||
| 								<div class="repo-user-list-block"> | ||||
| 									<ul class="menu-down-show menu-vertical menu-radius switching-list user-list" id="org-team-members-list"></ul> | ||||
| 								</div> | ||||
| 			                </form> | ||||
| 						</li> | ||||
| 						{{end}} | ||||
|                 		{{range .Team.Members}} | ||||
|                 		<li class="collab"> | ||||
|                 			{{if $.IsOrganizationOwner}} | ||||
| 							<a class="btn btn-small btn-red btn-radius right" href="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/remove?uid={{.Id}}">{{$.i18n.Tr "org.members.remove"}}</a> | ||||
| 							{{end}} | ||||
| 							<a class="member" href="/{{.Name}}"> | ||||
| 							    <img alt="{{.Name}}" class="pull-left avatar" src="{{.AvatarLink}}"> | ||||
| 							    <strong>{{.FullName}}</strong> ({{.Name}}) | ||||
| 							</a> | ||||
|                 		</li> | ||||
|                 		{{end}} | ||||
|                     </ul> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 		</div> | ||||
|   | ||||
| @@ -2,16 +2,21 @@ | ||||
| {{template "ng/base/header" .}} | ||||
| {{template "org/base/header" .}} | ||||
| <div id="repo-wrapper"> | ||||
|     <form id="team-create-form" class="form form-align panel panel-radius" action="{{.OrgLink}}/teams/new" method="post"> | ||||
|     <form id="team-create-form" class="form form-align panel panel-radius" action="{{if .PageIsOrgTeamsNew}}{{.OrgLink}}/teams/new{{else}}{{.OrgLink}}/teams/{{.Team.LowerName}}/edit{{end}}" data-delete-url="{{.OrgLink}}/teams/{{.Team.LowerName}}/delete" method="post"> | ||||
|         {{.CsrfTokenHtml}} | ||||
|         <div class="panel-header"> | ||||
|             <h2>{{.i18n.Tr "org.create_new_team"}}</h2> | ||||
|             <h2> | ||||
|                 {{if .PageIsOrgTeamsNew}}{{.i18n.Tr "org.create_new_team"}}{{else}}{{.i18n.Tr "org.teams.settings"}}{{end}} | ||||
|             </h2> | ||||
|         </div> | ||||
|         <div class="panel-content"> | ||||
|             {{template "ng/base/alert" .}} | ||||
|             <div class="field"> | ||||
|                 <label class="req" for="team-name">{{.i18n.Tr "org.team_name"}}</label> | ||||
|                 <input class="ipt ipt-large ipt-radius {{if .Err_TeamName}}ipt-error{{end}}" id="team-name" name="team_name" value="{{.team_name}}" required /> | ||||
|                 {{if eq .Team.LowerName "owners"}} | ||||
|                 <input type="hidden" name="team_name" value="{{.team_name}}"> | ||||
|                 {{end}} | ||||
|                 <input class="ipt ipt-large ipt-radius {{if .Err_TeamName}}ipt-error{{end}}" id="team-name" name="team_name" value="{{.team_name}}" required {{if eq .Team.LowerName "owners"}}disabled{{end}} /> | ||||
|                 <span class="form-label"></span> | ||||
|                 <span class="help">{{.i18n.Tr "org.team_name_helper"}}</span> | ||||
|             </div> | ||||
| @@ -21,6 +26,7 @@ | ||||
|                 <span class="form-label"></span> | ||||
|                 <span class="help">{{.i18n.Tr "org.team_desc_helper"}}</span> | ||||
|             </div> | ||||
|             {{if not (eq .Team.LowerName "owners")}} | ||||
|             <div class="field"> | ||||
|                 <h4 class="text-center">{{.i18n.Tr "org.team_permission_desc"}}</h4> | ||||
|                 <label></label> | ||||
| @@ -37,10 +43,19 @@ | ||||
|                 <p class="text-grey note">{{.i18n.Tr "org.teams.admin_access_helper"}}</p> | ||||
|             </div> | ||||
|             <hr> | ||||
|             {{end}} | ||||
|             <div class="field"> | ||||
|                 <label></label> | ||||
|                 <button class="btn btn-large btn-blue btn-radius">{{.i18n.Tr "org.create_new_team"}}</button> | ||||
|                 <a class="btn btn-small btn-gray btn-radius" id="repo-create-cancel" href="{{.OrgLink}}/teams"><strong>{{.i18n.Tr "cancel"}}</strong></a> | ||||
|                 {{if .PageIsOrgTeamsNew}} | ||||
|                     <button class="btn btn-large btn-blue btn-radius">{{.i18n.Tr "org.create_new_team"}}</button> | ||||
|                     <a class="btn btn-small btn-gray btn-radius" id="repo-create-cancel" href="{{.OrgLink}}/teams"><strong>{{.i18n.Tr "cancel"}}</strong></a> | ||||
|                 {{else}} | ||||
|                     <button class="btn btn-large btn-green btn-radius">{{.i18n.Tr "org.teams.update_settings"}}</button> | ||||
|                     {{if not (eq .Team.LowerName "owners")}} | ||||
|                           | ||||
|                     <button class="btn btn-large btn-red btn-radius" id="org-team-delete">{{.i18n.Tr "org.teams.delete_team"}}</button> | ||||
|                     {{end}} | ||||
|                 {{end}} | ||||
|             </div> | ||||
|         </div> | ||||
|     </form> | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| <div class="grid-1-3 panel panel-radius left" id="org-team-card"> | ||||
|     <div class="panel-header"> | ||||
| 		{{if .Team.IsMember $.SignedUser.Id}} | ||||
|         <a class="btn btn-small btn-red btn-header btn-radius right" href="{{.OrgLink}}/teams/{{.Team.LowerName}}/action/leave?page=team">{{$.i18n.Tr "org.teams.leave"}}</a> | ||||
|         {{else}} | ||||
|         <a class="btn btn-small btn-blue btn-header btn-radius right" href="{{.OrgLink}}/teams/{{.Team.LowerName}}/action/join?page=team">{{$.i18n.Tr "org.teams.join"}}</a> | ||||
|         <a class="btn btn-small btn-red btn-header btn-radius right" href="{{.OrgLink}}/teams/{{.Team.LowerName}}/action/leave?uid={{$.SignedUser.Id}}&page=team">{{$.i18n.Tr "org.teams.leave"}}</a> | ||||
|         {{else if .IsOrganizationOwner}} | ||||
|         <a class="btn btn-small btn-blue btn-header btn-radius right" href="{{.OrgLink}}/teams/{{.Team.LowerName}}/action/join?uid={{$.SignedUser.Id}}&page=team">{{$.i18n.Tr "org.teams.join"}}</a> | ||||
|         {{end}} | ||||
|     	<strong>{{.Team.Name}}</strong> | ||||
|     </div> | ||||
| @@ -11,16 +11,24 @@ | ||||
|     	<p class="desc">{{if .Team.Description}}{{.Team.Description}}{{else}}{{.i18n.Tr "org.teams.no_desc"}}{{end}}</p> | ||||
|     	<hr> | ||||
|     	<div class="team-stats"> | ||||
|     		<a class="text-black"><strong>{{.Team.NumMembers}}</strong> {{$.i18n.Tr "org.lower_members"}}</a> · | ||||
|     		<a class="text-black"><strong>{{.Team.NumRepos}}</strong> {{$.i18n.Tr "org.lower_repositories"}}</a> | ||||
|     		<a class="text-black" href="{{.OrgLink}}/teams/{{.Team.LowerName}}"><strong>{{.Team.NumMembers}}</strong> {{$.i18n.Tr "org.lower_members"}}</a> · | ||||
|     		<a class="text-black" href="{{.OrgLink}}/teams/{{.Team.LowerName}}/repositories"><strong>{{.Team.NumRepos}}</strong> {{$.i18n.Tr "org.lower_repositories"}}</a> | ||||
|     	</div> | ||||
|     	<p class="desc"> | ||||
|     		{{if eq .Team.LowerName "owners"}} | ||||
|     		{{.i18n.Tr "org.teams.owners_permission_desc" | Str2html}} | ||||
|             {{else if (eq .Team.Authorize 1)}} | ||||
|             {{.i18n.Tr "org.teams.read_permission_desc" | Str2html}} | ||||
|             {{else if (eq .Team.Authorize 2)}} | ||||
|             {{.i18n.Tr "org.teams.write_permission_desc" | Str2html}} | ||||
|             {{else if (eq .Team.Authorize 3)}} | ||||
|             {{.i18n.Tr "org.teams.admin_permission_desc" | Str2html}} | ||||
|     		{{end}} | ||||
|       	</p> | ||||
|     </div> | ||||
|     {{if .IsOrganizationOwner}} | ||||
|     <div class="panel-footer"> | ||||
|     	<a class="btn btn-medium btn-green btn-link btn-radius" href="{{.OrgLink}}/teams/{{.Team.LowerName}}/edit"><span class="octicon octicon-gear"></span> {{$.i18n.Tr "org.teams.settings"}}</a> | ||||
|     </div> | ||||
|     {{end}} | ||||
| </div> | ||||
| @@ -6,7 +6,7 @@ | ||||
| 		{{template "ng/base/alert" .}} | ||||
| 	</div> | ||||
| 	<div class="org-toolbar clear"> | ||||
| 		{{if .IsAdminTeam}} | ||||
| 		{{if .IsOrganizationOwner}} | ||||
|         <a class="btn btn-green btn-large btn-link btn-radius right" href="{{.OrgLink}}/teams/new"><i class="octicon octicon-repo-create"></i> {{.i18n.Tr "org.create_new_team"}}</a> | ||||
| 		{{end}} | ||||
| 	</div> | ||||
| @@ -16,9 +16,9 @@ | ||||
| 			<div class="panel panel-radius"> | ||||
| 				<div class="panel-header"> | ||||
| 					{{if .IsMember $.SignedUser.Id}} | ||||
|                     <a class="btn btn-small btn-red btn-header btn-radius right" href="{{$.OrgLink}}/teams/{{.LowerName}}/action/leave">{{$.i18n.Tr "org.teams.leave"}}</a> | ||||
|                     {{else}} | ||||
|                     <a class="btn btn-small btn-blue btn-header btn-radius right" href="{{$.OrgLink}}/teams/{{.LowerName}}/action/join">{{$.i18n.Tr "org.teams.join"}}</a> | ||||
|                     <a class="btn btn-small btn-red btn-header btn-radius right" href="{{$.OrgLink}}/teams/{{.LowerName}}/action/leave?uid={{$.SignedUser.Id}}">{{$.i18n.Tr "org.teams.leave"}}</a> | ||||
|                     {{else if $.IsOrganizationOwner}} | ||||
|                     <a class="btn btn-small btn-blue btn-header btn-radius right" href="{{$.OrgLink}}/teams/{{.LowerName}}/action/join?uid={{$.SignedUser.Id}}">{{$.i18n.Tr "org.teams.join"}}</a> | ||||
|                     {{end}} | ||||
|                     <a class="text-black" href="{{$.OrgLink}}/teams/{{.LowerName}}"><strong>{{.Name}}</strong></a> | ||||
| 				</div> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user