| @@ -48,7 +48,7 @@ and it takes care of all the other things for you`, | |||||
| Flags: []cli.Flag{}, | 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() { | func checkVersion() { | ||||
| data, err := ioutil.ReadFile(path.Join(setting.StaticRootPath, "templates/.VERSION")) | data, err := ioutil.ReadFile(path.Join(setting.StaticRootPath, "templates/.VERSION")) | ||||
| if err != nil { | if err != nil { | ||||
| @@ -235,7 +235,7 @@ func runWeb(*cli.Context) { | |||||
| r.Get("/members/action/:action", org.MembersAction) | r.Get("/members/action/:action", org.MembersAction) | ||||
| r.Get("/teams", org.Teams) | 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) | r.Get("/teams/:team/action/:action", org.TeamsAction) | ||||
| }, middleware.OrgAssignment(true, true)) | }, middleware.OrgAssignment(true, true)) | ||||
| @@ -243,6 +243,8 @@ func runWeb(*cli.Context) { | |||||
| r.Get("/teams/new", org.NewTeam) | r.Get("/teams/new", org.NewTeam) | ||||
| r.Post("/teams/new", bindIgnErr(auth.CreateTeamForm{}), org.NewTeamPost) | r.Post("/teams/new", bindIgnErr(auth.CreateTeamForm{}), org.NewTeamPost) | ||||
| r.Get("/teams/:team/edit", org.EditTeam) | 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) { | m.Group("/settings", func(r *macaron.Router) { | ||||
| r.Get("", org.Settings) | r.Get("", org.Settings) | ||||
| @@ -283,6 +283,13 @@ teams.no_desc = This team has no description | |||||
| teams.settings = Settings | 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.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.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] | [action] | ||||
| create_repo = created repository <a href="/%s">%s</a> | create_repo = created repository <a href="/%s">%s</a> | ||||
| @@ -283,6 +283,13 @@ teams.no_desc = 该团队暂无描述 | |||||
| teams.settings = 团队设置 | teams.settings = 团队设置 | ||||
| teams.owners_permission_desc = 管理员团队对 <strong>所有仓库</strong> 具有操作权限,且对组织具有 <strong>管理员权限</strong>。 | teams.owners_permission_desc = 管理员团队对 <strong>所有仓库</strong> 具有操作权限,且对组织具有 <strong>管理员权限</strong>。 | ||||
| teams.members = 团队成员 | 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] | [action] | ||||
| create_repo = 创建了仓库 <a href="/%s">%s</a> | create_repo = 创建了仓库 <a href="/%s">%s</a> | ||||
| @@ -17,7 +17,7 @@ import ( | |||||
| "github.com/gogits/gogs/modules/setting" | "github.com/gogits/gogs/modules/setting" | ||||
| ) | ) | ||||
| const APP_VER = "0.4.7.0823 Alpha" | |||||
| const APP_VER = "0.4.7.0824 Alpha" | |||||
| func init() { | func init() { | ||||
| runtime.GOMAXPROCS(runtime.NumCPU()) | runtime.GOMAXPROCS(runtime.NumCPU()) | ||||
| @@ -6,11 +6,13 @@ package models | |||||
| import ( | import ( | ||||
| "errors" | "errors" | ||||
| "fmt" | |||||
| "os" | "os" | ||||
| "path" | "path" | ||||
| "strings" | "strings" | ||||
| "github.com/Unknwon/com" | "github.com/Unknwon/com" | ||||
| "github.com/go-xorm/xorm" | |||||
| "github.com/gogits/gogs/modules/base" | "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. | // Add initial creator to organization and owner team. | ||||
| ou := &OrgUser{ | 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 { | if _, err = sess.Insert(ou); err != nil { | ||||
| sess.Rollback() | sess.Rollback() | ||||
| @@ -199,7 +201,7 @@ type OrgUser struct { | |||||
| OrgId int64 `xorm:"INDEX UNIQUE(s)"` | OrgId int64 `xorm:"INDEX UNIQUE(s)"` | ||||
| IsPublic bool | IsPublic bool | ||||
| IsOwner bool | IsOwner bool | ||||
| NumTeam int | |||||
| NumTeams int | |||||
| } | } | ||||
| // IsOrganizationOwner returns true if given user is in the owner team. | // IsOrganizationOwner returns true if given user is in the owner team. | ||||
| @@ -255,17 +257,17 @@ func AddOrgUser(orgId, uid int64) error { | |||||
| return nil | return nil | ||||
| } | } | ||||
| ou := &OrgUser{ | |||||
| Uid: uid, | |||||
| OrgId: orgId, | |||||
| } | |||||
| sess := x.NewSession() | sess := x.NewSession() | ||||
| defer sess.Close() | defer sess.Close() | ||||
| if err := sess.Begin(); err != nil { | if err := sess.Begin(); err != nil { | ||||
| return err | return err | ||||
| } | } | ||||
| ou := &OrgUser{ | |||||
| Uid: uid, | |||||
| OrgId: orgId, | |||||
| } | |||||
| if _, err := sess.Insert(ou); err != nil { | if _, err := sess.Insert(ou); err != nil { | ||||
| sess.Rollback() | sess.Rollback() | ||||
| return err | return err | ||||
| @@ -288,12 +290,17 @@ func RemoveOrgUser(orgId, uid int64) error { | |||||
| return nil | 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. | // Check if the user to delete is the last member in owner team. | ||||
| if IsOrganizationOwner(orgId, uid) { | if IsOrganizationOwner(orgId, uid) { | ||||
| org, err := GetUserById(orgId) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| t, err := org.GetOwnerTeam() | t, err := org.GetOwnerTeam() | ||||
| if err != nil { | if err != nil { | ||||
| return err | return err | ||||
| @@ -317,6 +324,33 @@ func RemoveOrgUser(orgId, uid int64) error { | |||||
| return err | 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() | return sess.Commit() | ||||
| } | } | ||||
| @@ -352,6 +386,11 @@ type Team struct { | |||||
| NumMembers int | 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. | // IsTeamMember returns true if given user is a member of team. | ||||
| func (t *Team) IsMember(uid int64) bool { | func (t *Team) IsMember(uid int64) bool { | ||||
| return IsTeamMember(t.OrgId, t.Id, uid) | return IsTeamMember(t.OrgId, t.Id, uid) | ||||
| @@ -362,7 +401,10 @@ func (t *Team) GetRepositories() error { | |||||
| idStrs := strings.Split(t.RepoIds, "|") | idStrs := strings.Split(t.RepoIds, "|") | ||||
| t.Repos = make([]*Repository, 0, len(idStrs)) | t.Repos = make([]*Repository, 0, len(idStrs)) | ||||
| for _, str := range idStrs { | for _, str := range idStrs { | ||||
| id := com.StrTo(str).MustInt64() | |||||
| if len(str) == 0 { | |||||
| continue | |||||
| } | |||||
| id := com.StrTo(str[1:]).MustInt64() | |||||
| if id == 0 { | if id == 0 { | ||||
| continue | continue | ||||
| } | } | ||||
| @@ -459,15 +501,177 @@ func GetTeamById(teamId int64) (*Team, error) { | |||||
| return t, nil | 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. | // 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 { | if len(t.Description) > 255 { | ||||
| t.Description = 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) | 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 | 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. | // AddTeamMember adds new member to given team of given organization. | ||||
| func AddTeamMember(orgId, teamId, uid int64) error { | func AddTeamMember(orgId, teamId, uid int64) error { | ||||
| if !IsOrganizationMember(orgId, uid) || IsTeamMember(orgId, teamId, uid) { | |||||
| if IsTeamMember(orgId, teamId, uid) { | |||||
| return nil | return nil | ||||
| } | } | ||||
| if err := AddOrgUser(orgId, uid); err != nil { | |||||
| return err | |||||
| } | |||||
| // Get team and its repositories. | // Get team and its repositories. | ||||
| t, err := GetTeamById(teamId) | t, err := GetTeamById(teamId) | ||||
| if err != nil { | if err != nil { | ||||
| @@ -569,18 +798,49 @@ func AddTeamMember(orgId, teamId, uid int64) error { | |||||
| // Give access to team repositories. | // Give access to team repositories. | ||||
| for _, repo := range t.Repos { | 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() | sess.Rollback() | ||||
| return err | 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() | 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) { | if !IsTeamMember(orgId, teamId, uid) { | ||||
| return nil | return nil | ||||
| } | } | ||||
| @@ -590,6 +850,12 @@ func RemoveTeamMember(orgId, teamId, uid int64) error { | |||||
| if err != nil { | if err != nil { | ||||
| return err | 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-- | t.NumMembers-- | ||||
| if err = t.GetRepositories(); err != nil { | if err = t.GetRepositories(); err != nil { | ||||
| @@ -608,22 +874,12 @@ func RemoveTeamMember(orgId, teamId, uid int64) error { | |||||
| return err | return err | ||||
| } | } | ||||
| sess := x.NewSession() | |||||
| defer sess.Close() | |||||
| if err := sess.Begin(); err != nil { | |||||
| return err | |||||
| } | |||||
| tu := &TeamUser{ | tu := &TeamUser{ | ||||
| Uid: uid, | Uid: uid, | ||||
| OrgId: orgId, | OrgId: orgId, | ||||
| TeamId: teamId, | TeamId: teamId, | ||||
| } | } | ||||
| access := &Access{ | |||||
| UserName: u.LowerName, | |||||
| } | |||||
| if _, err := sess.Delete(tu); err != nil { | if _, err := sess.Delete(tu); err != nil { | ||||
| sess.Rollback() | sess.Rollback() | ||||
| return err | return err | ||||
| @@ -633,13 +889,63 @@ func RemoveTeamMember(orgId, teamId, uid int64) error { | |||||
| } | } | ||||
| // Delete access to team repositories. | // Delete access to team repositories. | ||||
| access := &Access{ | |||||
| UserName: u.LowerName, | |||||
| } | |||||
| for _, repo := range t.Repos { | 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() | sess.Rollback() | ||||
| return err | 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() | return sess.Commit() | ||||
| } | } | ||||
| @@ -525,6 +525,7 @@ func CreateRepository(u *User, name, desc, lang, license string, private, mirror | |||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| for _, u := range us { | for _, u := range us { | ||||
| access.Id = 0 | |||||
| access.UserName = u.LowerName | access.UserName = u.LowerName | ||||
| if _, err = sess.Insert(access); err != nil { | if _, err = sess.Insert(access); err != nil { | ||||
| sess.Rollback() | 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. | // ChangeRepositoryName changes all corresponding setting from old repository name to new one. | ||||
| func ChangeRepositoryName(userName, oldRepoName, newRepoName string) (err error) { | func ChangeRepositoryName(userName, oldRepoName, newRepoName string) (err error) { | ||||
| if !IsLegalName(newRepoName) { | |||||
| return ErrRepoNameIllegal | |||||
| } | |||||
| // Update accesses. | // Update accesses. | ||||
| accesses := make([]Access, 0, 10) | accesses := make([]Access, 0, 10) | ||||
| if err = x.Find(&accesses, &Access{RepoName: strings.ToLower(userName + "/" + oldRepoName)}); err != nil { | 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"` | LoginSource int64 `xorm:"not null default 0"` | ||||
| LoginName string | LoginName string | ||||
| Type UserType | Type UserType | ||||
| Orgs []*User `xorm:"-"` | |||||
| Orgs []*User `xorm:"-"` | |||||
| Repos []*Repository `xorm:"-"` | |||||
| NumFollowers int | NumFollowers int | ||||
| NumFollowings int | NumFollowings int | ||||
| NumStars int | NumStars int | ||||
| @@ -143,6 +144,12 @@ func (u *User) GetOrganizationCount() (int64, error) { | |||||
| return x.Where("uid=?", u.Id).Count(new(OrgUser)) | 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. | // GetOrganizations returns all organizations that user belongs to. | ||||
| func (u *User) GetOrganizations() error { | func (u *User) GetOrganizations() error { | ||||
| ous, err := GetOrgUsersByUserId(u.Id) | ous, err := GetOrgUsersByUserId(u.Id) | ||||
| @@ -46,6 +46,7 @@ type Context struct { | |||||
| IsBranch bool | IsBranch bool | ||||
| IsTag bool | IsTag bool | ||||
| IsCommit bool | IsCommit bool | ||||
| IsAdmin bool // Current user is admin level. | |||||
| HasAccess bool | HasAccess bool | ||||
| Repository *models.Repository | Repository *models.Repository | ||||
| Owner *models.User | Owner *models.User | ||||
| @@ -8,6 +8,7 @@ import ( | |||||
| "github.com/Unknwon/macaron" | "github.com/Unknwon/macaron" | ||||
| "github.com/gogits/gogs/models" | "github.com/gogits/gogs/models" | ||||
| "github.com/gogits/gogs/modules/log" | |||||
| ) | ) | ||||
| func OrgAssignment(redirect bool, args ...bool) macaron.Handler { | func OrgAssignment(redirect bool, args ...bool) macaron.Handler { | ||||
| @@ -35,6 +36,7 @@ func OrgAssignment(redirect bool, args ...bool) macaron.Handler { | |||||
| if err == models.ErrUserNotExist { | if err == models.ErrUserNotExist { | ||||
| ctx.Handle(404, "GetUserByName", err) | ctx.Handle(404, "GetUserByName", err) | ||||
| } else if redirect { | } else if redirect { | ||||
| log.Error(4, "GetUserByName", err) | |||||
| ctx.Redirect("/") | ctx.Redirect("/") | ||||
| } else { | } else { | ||||
| ctx.Handle(500, "GetUserByName", err) | ctx.Handle(500, "GetUserByName", err) | ||||
| @@ -52,17 +54,14 @@ func OrgAssignment(redirect bool, args ...bool) macaron.Handler { | |||||
| } else { | } else { | ||||
| if org.IsOrgMember(ctx.User.Id) { | if org.IsOrgMember(ctx.User.Id) { | ||||
| ctx.Org.IsMember = true | ctx.Org.IsMember = true | ||||
| // TODO: ctx.Org.IsAdminTeam | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| if (requireMember && !ctx.Org.IsMember) || | if (requireMember && !ctx.Org.IsMember) || | ||||
| (requireOwner && !ctx.Org.IsOwner) || | |||||
| (requireAdminTeam && !ctx.Org.IsAdminTeam) { | |||||
| (requireOwner && !ctx.Org.IsOwner) { | |||||
| ctx.Handle(404, "OrgAssignment", err) | ctx.Handle(404, "OrgAssignment", err) | ||||
| return | return | ||||
| } | } | ||||
| ctx.Data["IsAdminTeam"] = ctx.Org.IsAdminTeam | |||||
| ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner | ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner | ||||
| ctx.Org.OrgLink = "/org/" + org.Name | ctx.Org.OrgLink = "/org/" + org.Name | ||||
| @@ -76,6 +75,7 @@ func OrgAssignment(redirect bool, args ...bool) macaron.Handler { | |||||
| if err == models.ErrTeamNotExist { | if err == models.ErrTeamNotExist { | ||||
| ctx.Handle(404, "GetTeam", err) | ctx.Handle(404, "GetTeam", err) | ||||
| } else if redirect { | } else if redirect { | ||||
| log.Error(4, "GetTeam", err) | |||||
| ctx.Redirect("/") | ctx.Redirect("/") | ||||
| } else { | } else { | ||||
| ctx.Handle(500, "GetTeam", err) | ctx.Handle(500, "GetTeam", err) | ||||
| @@ -83,6 +83,12 @@ func OrgAssignment(redirect bool, args ...bool) macaron.Handler { | |||||
| return | return | ||||
| } | } | ||||
| ctx.Data["Team"] = ctx.Org.Team | 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 { | if err == models.ErrUserNotExist { | ||||
| ctx.Handle(404, "GetUserByName", err) | ctx.Handle(404, "GetUserByName", err) | ||||
| } else if redirect { | } else if redirect { | ||||
| log.Error(4, "GetUserByName", err) | |||||
| ctx.Redirect("/") | ctx.Redirect("/") | ||||
| } else { | } else { | ||||
| ctx.Handle(500, "GetUserByName", err) | ctx.Handle(500, "GetUserByName", err) | ||||
| @@ -84,7 +85,7 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler { | |||||
| ctx.Repo.IsTrueOwner = true | ctx.Repo.IsTrueOwner = true | ||||
| } | } | ||||
| // get repository | |||||
| // Get repository. | |||||
| repo, err := models.GetRepositoryByName(u.Id, repoName) | repo, err := models.GetRepositoryByName(u.Id, repoName) | ||||
| if err != nil { | if err != nil { | ||||
| if err == models.ErrRepoNotExist { | 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). | // 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. | // Check access. | ||||
| @@ -281,7 +296,7 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler { | |||||
| func RequireTrueOwner() macaron.Handler { | func RequireTrueOwner() macaron.Handler { | ||||
| return func(ctx *Context) { | return func(ctx *Context) { | ||||
| if !ctx.Repo.IsTrueOwner { | |||||
| if !ctx.Repo.IsTrueOwner && !ctx.Repo.IsAdmin { | |||||
| if !ctx.IsSigned { | if !ctx.IsSigned { | ||||
| ctx.SetCookie("redirect_to", "/"+url.QueryEscape(ctx.Req.RequestURI)) | ctx.SetCookie("redirect_to", "/"+url.QueryEscape(ctx.Req.RequestURI)) | ||||
| ctx.Redirect("/user/login") | ctx.Redirect("/user/login") | ||||
| @@ -1298,27 +1298,33 @@ The register and sign-in page style | |||||
| .repo-setting-zone { | .repo-setting-zone { | ||||
| padding: 30px; | padding: 30px; | ||||
| } | } | ||||
| #team-members-list, | |||||
| #repo-collab-list { | #repo-collab-list { | ||||
| list-style: none; | list-style: none; | ||||
| padding: 10px 0 5px 0; | padding: 10px 0 5px 0; | ||||
| } | } | ||||
| #team-members-list li.collab, | |||||
| #repo-collab-list li.collab { | #repo-collab-list li.collab { | ||||
| clear: both; | clear: both; | ||||
| height: 50px; | height: 50px; | ||||
| padding: 0 15px 0 15px; | padding: 0 15px 0 15px; | ||||
| } | } | ||||
| #team-members-list a.member, | |||||
| #repo-collab-list a.member { | #repo-collab-list a.member { | ||||
| color: #444; | color: #444; | ||||
| height: 50px; | height: 50px; | ||||
| line-height: 50px; | line-height: 50px; | ||||
| } | } | ||||
| #team-members-list a.member:hover, | |||||
| #repo-collab-list a.member:hover { | #repo-collab-list a.member:hover { | ||||
| color: #4183C4; | color: #4183C4; | ||||
| } | } | ||||
| #team-members-list .avatar, | |||||
| #repo-collab-list .avatar { | #repo-collab-list .avatar { | ||||
| margin-right: 1em; | margin-right: 1em; | ||||
| width: 40px; | width: 40px; | ||||
| } | } | ||||
| #team-members-list .remove-collab, | |||||
| #repo-collab-list .remove-collab { | #repo-collab-list .remove-collab { | ||||
| color: #DD4B39; | color: #DD4B39; | ||||
| } | } | ||||
| @@ -1871,3 +1877,14 @@ textarea#issue-add-content { | |||||
| #org-team-card .panel-footer { | #org-team-card .panel-footer { | ||||
| padding: 10px 20px; | 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 () { | $(document).ready(function () { | ||||
| initCore(); | initCore(); | ||||
| if ($('#user-profile-setting').length) { | if ($('#user-profile-setting').length) { | ||||
| @@ -368,6 +403,12 @@ $(document).ready(function () { | |||||
| if ($('#invite-box').length) { | if ($('#invite-box').length) { | ||||
| initInvite(); | initInvite(); | ||||
| } | } | ||||
| if ($('#team-create-form').length) { | |||||
| initOrgTeamCreate(); | |||||
| } | |||||
| if ($('#team-members-list').length) { | |||||
| initTeamMembersList(); | |||||
| } | |||||
| Tabs('#dashboard-sidebar-menu'); | Tabs('#dashboard-sidebar-menu'); | ||||
| @@ -196,4 +196,19 @@ | |||||
| .panel-footer { | .panel-footer { | ||||
| padding: 10px 20px; | 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 { | .repo-setting-zone { | ||||
| padding: 30px; | padding: 30px; | ||||
| } | } | ||||
| #team-members-list, | |||||
| #repo-collab-list { | #repo-collab-list { | ||||
| list-style: none; | list-style: none; | ||||
| padding: 10px 0 5px 0; | padding: 10px 0 5px 0; | ||||
| @@ -82,7 +82,12 @@ func MembersAction(ctx *middleware.Context) { | |||||
| }) | }) | ||||
| return | 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) { | func Invitation(ctx *middleware.Context) { | ||||
| @@ -5,6 +5,8 @@ | |||||
| package org | package org | ||||
| import ( | import ( | ||||
| "github.com/Unknwon/com" | |||||
| "github.com/gogits/gogs/models" | "github.com/gogits/gogs/models" | ||||
| "github.com/gogits/gogs/modules/auth" | "github.com/gogits/gogs/modules/auth" | ||||
| "github.com/gogits/gogs/modules/base" | "github.com/gogits/gogs/modules/base" | ||||
| @@ -39,23 +41,71 @@ func Teams(ctx *middleware.Context) { | |||||
| } | } | ||||
| func TeamsAction(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 | var err error | ||||
| switch ctx.Params(":action") { | switch ctx.Params(":action") { | ||||
| case "join": | case "join": | ||||
| if !ctx.Org.IsOwner { | |||||
| ctx.Error(404) | |||||
| return | |||||
| } | |||||
| err = ctx.Org.Team.AddMember(ctx.User.Id) | err = ctx.Org.Team.AddMember(ctx.User.Id) | ||||
| case "leave": | case "leave": | ||||
| err = ctx.Org.Team.RemoveMember(ctx.User.Id) | 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 { | 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) { | 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) | ctx.Redirect(ctx.Org.OrgLink + "/teams/" + t.LowerName) | ||||
| } | } | ||||
| 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) { | func EditTeam(ctx *middleware.Context) { | ||||
| ctx.Data["Title"] = "Organization " + ctx.Params(":org") + " Edit Team" | |||||
| ctx.HTML(200, "org/edit_team") | |||||
| 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 SingleTeam(ctx *middleware.Context) { | |||||
| ctx.Data["Title"] = ctx.Org.Team.Name | |||||
| 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["PageIsOrgTeams"] = true | ||||
| ctx.HTML(200, TEAM_MEMBERS) | |||||
| 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) | ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), SETTINGS_OPTIONS, nil) | ||||
| return | return | ||||
| } else if err = models.ChangeRepositoryName(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name, newRepoName); err != nil { | } 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 | return | ||||
| } | } | ||||
| log.Trace("Repository name changed: %s/%s -> %s", ctx.Repo.Owner.Name, ctx.Repo.Repository.Name, newRepoName) | 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. | // Delete collaborator. | ||||
| remove := strings.ToLower(ctx.Query("remove")) | remove := strings.ToLower(ctx.Query("remove")) | ||||
| if len(remove) > 0 && remove != ctx.Repo.Owner.LowerName { | 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.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success")) | ||||
| ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") | 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> | </div> | ||||
| <div class="container"> | <div class="container"> | ||||
| {{$isMember := .Org.IsOrgMember $.SignedUser.Id}} | |||||
| <div id="org-home-repo-list" class="left grid-2-3"> | <div id="org-home-repo-list" class="left grid-2-3"> | ||||
| <div class="clear"> | <div class="clear"> | ||||
| {{if .IsOrganizationOwner}} | {{if .IsOrganizationOwner}} | ||||
| @@ -26,6 +27,7 @@ | |||||
| </div> | </div> | ||||
| <div id="org-repo-list"> | <div id="org-repo-list"> | ||||
| {{range .Repos}} | {{range .Repos}} | ||||
| {{if or $isMember (not .IsPrivate)}} | |||||
| <div class="org-repo-item"> | <div class="org-repo-item"> | ||||
| <ul class="org-repo-status right"> | <ul class="org-repo-status right"> | ||||
| <li><i class="octicon octicon-star"></i> {{.NumStars}}</li> | <li><i class="octicon octicon-star"></i> {{.NumStars}}</li> | ||||
| @@ -35,6 +37,7 @@ | |||||
| <p class="org-repo-description">{{.Description}}</p> | <p class="org-repo-description">{{.Description}}</p> | ||||
| <p class="org-repo-updated">{{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Updated $.i18n.Lang}}</p> | <p class="org-repo-updated">{{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Updated $.i18n.Lang}}</p> | ||||
| </div> | </div> | ||||
| {{end}} | |||||
| {{end}} | {{end}} | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -42,12 +45,16 @@ | |||||
| <div class="org-sidebar"> | <div class="org-sidebar"> | ||||
| <div class="panel panel-radius"> | <div class="panel panel-radius"> | ||||
| <div class="panel-header"> | <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> | <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> | <strong>{{.i18n.Tr "org.people"}}</strong> | ||||
| </div> | </div> | ||||
| <div class="panel-body member-avatar-group"> | <div class="panel-body member-avatar-group"> | ||||
| {{range .Members}} | {{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}} | {{end}} | ||||
| </div> | </div> | ||||
| {{if .IsOrganizationOwner}} | {{if .IsOrganizationOwner}} | ||||
| @@ -56,6 +63,7 @@ | |||||
| </div> | </div> | ||||
| {{end}} | {{end}} | ||||
| </div> | </div> | ||||
| {{if $isMember}} | |||||
| <br> | <br> | ||||
| <div class="panel panel-radius"> | <div class="panel panel-radius"> | ||||
| <div class="panel-header"> | <div class="panel-header"> | ||||
| @@ -76,9 +84,9 @@ | |||||
| <div class="panel-footer"> | <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> | <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> | </div> | ||||
| {{end}} | {{end}} | ||||
| </div> | </div> | ||||
| {{end}} | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -6,7 +6,7 @@ | |||||
| {{template "ng/base/alert" .}} | {{template "ng/base/alert" .}} | ||||
| </div> | </div> | ||||
| <div class="org-toolbar clear"> | <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> | <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}} | {{end}} | ||||
| </div> | </div> | ||||
| @@ -2,7 +2,8 @@ | |||||
| {{template "ng/base/header" .}} | {{template "ng/base/header" .}} | ||||
| {{template "org/base/header" .}} | {{template "org/base/header" .}} | ||||
| <div id="setting-wrapper" class="main-wrapper"> | <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" .}} | {{template "org/team/sidebar" .}} | ||||
| <div class="grid-2-3 left"> | <div class="grid-2-3 left"> | ||||
| <div class="setting-content"> | <div class="setting-content"> | ||||
| @@ -10,6 +11,32 @@ | |||||
| <div class="panel-header"> | <div class="panel-header"> | ||||
| {{.i18n.Tr "org.teams.members"}} | {{.i18n.Tr "org.teams.members"}} | ||||
| </div> | </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> | </div> | ||||
| </div> | </div> | ||||
| @@ -2,16 +2,21 @@ | |||||
| {{template "ng/base/header" .}} | {{template "ng/base/header" .}} | ||||
| {{template "org/base/header" .}} | {{template "org/base/header" .}} | ||||
| <div id="repo-wrapper"> | <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}} | {{.CsrfTokenHtml}} | ||||
| <div class="panel-header"> | <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> | ||||
| <div class="panel-content"> | <div class="panel-content"> | ||||
| {{template "ng/base/alert" .}} | {{template "ng/base/alert" .}} | ||||
| <div class="field"> | <div class="field"> | ||||
| <label class="req" for="team-name">{{.i18n.Tr "org.team_name"}}</label> | <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="form-label"></span> | ||||
| <span class="help">{{.i18n.Tr "org.team_name_helper"}}</span> | <span class="help">{{.i18n.Tr "org.team_name_helper"}}</span> | ||||
| </div> | </div> | ||||
| @@ -21,6 +26,7 @@ | |||||
| <span class="form-label"></span> | <span class="form-label"></span> | ||||
| <span class="help">{{.i18n.Tr "org.team_desc_helper"}}</span> | <span class="help">{{.i18n.Tr "org.team_desc_helper"}}</span> | ||||
| </div> | </div> | ||||
| {{if not (eq .Team.LowerName "owners")}} | |||||
| <div class="field"> | <div class="field"> | ||||
| <h4 class="text-center">{{.i18n.Tr "org.team_permission_desc"}}</h4> | <h4 class="text-center">{{.i18n.Tr "org.team_permission_desc"}}</h4> | ||||
| <label></label> | <label></label> | ||||
| @@ -37,10 +43,19 @@ | |||||
| <p class="text-grey note">{{.i18n.Tr "org.teams.admin_access_helper"}}</p> | <p class="text-grey note">{{.i18n.Tr "org.teams.admin_access_helper"}}</p> | ||||
| </div> | </div> | ||||
| <hr> | <hr> | ||||
| {{end}} | |||||
| <div class="field"> | <div class="field"> | ||||
| <label></label> | <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> | ||||
| </div> | </div> | ||||
| </form> | </form> | ||||
| @@ -1,9 +1,9 @@ | |||||
| <div class="grid-1-3 panel panel-radius left" id="org-team-card"> | <div class="grid-1-3 panel panel-radius left" id="org-team-card"> | ||||
| <div class="panel-header"> | <div class="panel-header"> | ||||
| {{if .Team.IsMember $.SignedUser.Id}} | {{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}} | {{end}} | ||||
| <strong>{{.Team.Name}}</strong> | <strong>{{.Team.Name}}</strong> | ||||
| </div> | </div> | ||||
| @@ -11,16 +11,24 @@ | |||||
| <p class="desc">{{if .Team.Description}}{{.Team.Description}}{{else}}{{.i18n.Tr "org.teams.no_desc"}}{{end}}</p> | <p class="desc">{{if .Team.Description}}{{.Team.Description}}{{else}}{{.i18n.Tr "org.teams.no_desc"}}{{end}}</p> | ||||
| <hr> | <hr> | ||||
| <div class="team-stats"> | <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> | </div> | ||||
| <p class="desc"> | <p class="desc"> | ||||
| {{if eq .Team.LowerName "owners"}} | {{if eq .Team.LowerName "owners"}} | ||||
| {{.i18n.Tr "org.teams.owners_permission_desc" | Str2html}} | {{.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}} | {{end}} | ||||
| </p> | </p> | ||||
| </div> | </div> | ||||
| {{if .IsOrganizationOwner}} | |||||
| <div class="panel-footer"> | <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> | <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> | </div> | ||||
| {{end}} | |||||
| </div> | </div> | ||||
| @@ -6,7 +6,7 @@ | |||||
| {{template "ng/base/alert" .}} | {{template "ng/base/alert" .}} | ||||
| </div> | </div> | ||||
| <div class="org-toolbar clear"> | <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> | <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}} | {{end}} | ||||
| </div> | </div> | ||||
| @@ -16,9 +16,9 @@ | |||||
| <div class="panel panel-radius"> | <div class="panel panel-radius"> | ||||
| <div class="panel-header"> | <div class="panel-header"> | ||||
| {{if .IsMember $.SignedUser.Id}} | {{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}} | {{end}} | ||||
| <a class="text-black" href="{{$.OrgLink}}/teams/{{.LowerName}}"><strong>{{.Name}}</strong></a> | <a class="text-black" href="{{$.OrgLink}}/teams/{{.LowerName}}"><strong>{{.Name}}</strong></a> | ||||
| </div> | </div> | ||||