mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-14 17:27:39 +02:00
add group pages to web router
- `/{username}/groups/{group_id}` -> show a group's homepage with its repositories
- `/{org}/groups/new` -> create a new group in an org
- `/{org}/groups/{group_id}` -> show a group in an org
- `/{org}/groups/{group_id}/teams` -> show/edit teams with access to a group
- `/{org}/groups/{group_id}/teams/{team}/edit` -> edit a team's access to a group
- `/{org}/group/{group_id}/settings` -> show/edit a group's settings
- `/group/search` -> search for groups with parameters. return matching groups and repos contained within them
This commit is contained in:
parent
97576a41c4
commit
efe8193237
45
routers/web/group/edit.go
Normal file
45
routers/web/group/edit.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package group
|
||||||
|
|
||||||
|
import (
|
||||||
|
group_model "code.gitea.io/gitea/models/group"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/modules/json"
|
||||||
|
"code.gitea.io/gitea/services/context"
|
||||||
|
"code.gitea.io/gitea/services/forms"
|
||||||
|
group_service "code.gitea.io/gitea/services/group"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MoveGroupItem(ctx *context.Context) {
|
||||||
|
form := &forms.MovedGroupItemForm{}
|
||||||
|
if err := json.NewDecoder(ctx.Req.Body).Decode(form); err != nil {
|
||||||
|
ctx.ServerError("DecodeMovedGroupItemForm", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if form.IsGroup {
|
||||||
|
group, err := group_model.GetGroupByID(ctx, form.ItemID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetGroupByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if group.ParentGroupID != form.NewParent {
|
||||||
|
if err = group_model.MoveGroup(ctx, group, form.NewParent, form.NewPos); err != nil {
|
||||||
|
ctx.ServerError("MoveGroup", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = group_service.RecalculateGroupAccess(ctx, group, false); err != nil {
|
||||||
|
ctx.ServerError("RecalculateGroupAccess", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
repo, err := repo_model.GetRepositoryByID(ctx, form.ItemID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetRepositoryByID", err)
|
||||||
|
}
|
||||||
|
if repo.GroupID != form.NewParent {
|
||||||
|
if err = group_service.MoveRepositoryToGroup(ctx, repo, form.NewParent, form.NewPos); err != nil {
|
||||||
|
ctx.ServerError("MoveRepositoryToGroup", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.JSONOK()
|
||||||
|
}
|
||||||
117
routers/web/group/home.go
Normal file
117
routers/web/group/home.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package group
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/models/organization"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
shared_group_model "code.gitea.io/gitea/models/shared/group"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
shared_group "code.gitea.io/gitea/routers/web/shared/group"
|
||||||
|
"code.gitea.io/gitea/services/context"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tplGroupHome = "group/home"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Home(ctx *context.Context) {
|
||||||
|
org := ctx.Org.Organization
|
||||||
|
|
||||||
|
ctx.Data["PageIsUserProfile"] = true
|
||||||
|
ctx.Data["Title"] = org.DisplayName()
|
||||||
|
|
||||||
|
var orderBy db.SearchOrderBy
|
||||||
|
sortOrder := ctx.FormString("sort")
|
||||||
|
if _, ok := repo_model.OrderByFlatMap[sortOrder]; !ok {
|
||||||
|
sortOrder = setting.UI.ExploreDefaultSort
|
||||||
|
}
|
||||||
|
ctx.Data["SortType"] = sortOrder
|
||||||
|
orderBy = repo_model.OrderByFlatMap[sortOrder]
|
||||||
|
|
||||||
|
keyword := ctx.FormTrim("q")
|
||||||
|
ctx.Data["Keyword"] = keyword
|
||||||
|
|
||||||
|
language := ctx.FormTrim("language")
|
||||||
|
ctx.Data["Language"] = language
|
||||||
|
|
||||||
|
page := ctx.FormInt("page")
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
archived := ctx.FormOptionalBool("archived")
|
||||||
|
ctx.Data["IsArchived"] = archived
|
||||||
|
|
||||||
|
fork := ctx.FormOptionalBool("fork")
|
||||||
|
ctx.Data["IsFork"] = fork
|
||||||
|
|
||||||
|
mirror := ctx.FormOptionalBool("mirror")
|
||||||
|
ctx.Data["IsMirror"] = mirror
|
||||||
|
|
||||||
|
template := ctx.FormOptionalBool("template")
|
||||||
|
ctx.Data["IsTemplate"] = template
|
||||||
|
|
||||||
|
private := ctx.FormOptionalBool("private")
|
||||||
|
ctx.Data["IsPrivate"] = private
|
||||||
|
|
||||||
|
err := shared_group.LoadHeaderCount(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("LoadHeaderCount", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := &organization.FindOrgMembersOpts{
|
||||||
|
Doer: ctx.Doer,
|
||||||
|
OrgID: org.ID,
|
||||||
|
IsDoerMember: ctx.Org.IsMember,
|
||||||
|
ListOptions: db.ListOptions{Page: 1, PageSize: 25},
|
||||||
|
}
|
||||||
|
|
||||||
|
members, err := shared_group_model.FindGroupMembers(ctx, ctx.RepoGroup.Group.ID, opts)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("FindOrgMembers", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Data["Members"] = members
|
||||||
|
ctx.Data["Teams"] = ctx.RepoGroup.Teams
|
||||||
|
ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull
|
||||||
|
ctx.Data["ShowMemberAndTeamTab"] = ctx.RepoGroup.IsMember || len(members) > 0
|
||||||
|
ctx.Data["PageIsViewRepositories"] = true
|
||||||
|
|
||||||
|
var (
|
||||||
|
repos []*repo_model.Repository
|
||||||
|
count int64
|
||||||
|
)
|
||||||
|
repos, count, err = repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{
|
||||||
|
ListOptions: db.ListOptions{
|
||||||
|
PageSize: setting.UI.User.RepoPagingNum,
|
||||||
|
Page: page,
|
||||||
|
},
|
||||||
|
Keyword: keyword,
|
||||||
|
OwnerID: org.ID,
|
||||||
|
OrderBy: orderBy,
|
||||||
|
Private: ctx.IsSigned,
|
||||||
|
Actor: ctx.Doer,
|
||||||
|
Language: language,
|
||||||
|
IncludeDescription: setting.UI.SearchRepoDescription,
|
||||||
|
Archived: archived,
|
||||||
|
Fork: fork,
|
||||||
|
Mirror: mirror,
|
||||||
|
Template: template,
|
||||||
|
IsPrivate: private,
|
||||||
|
GroupID: ctx.RepoGroup.Group.ID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("SearchRepository", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Data["Repos"] = repos
|
||||||
|
ctx.Data["Total"] = count
|
||||||
|
|
||||||
|
pager := context.NewPagination(int(count), setting.UI.User.RepoPagingNum, page, 5)
|
||||||
|
pager.AddParamFromRequest(ctx.Req)
|
||||||
|
ctx.Data["Page"] = pager
|
||||||
|
ctx.HTML(http.StatusOK, tplGroupHome)
|
||||||
|
}
|
||||||
89
routers/web/group/new.go
Normal file
89
routers/web/group/new.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package group
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/models/perm"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
group_model "code.gitea.io/gitea/models/group"
|
||||||
|
unit_model "code.gitea.io/gitea/models/unit"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/optional"
|
||||||
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
||||||
|
"code.gitea.io/gitea/services/context"
|
||||||
|
"code.gitea.io/gitea/services/forms"
|
||||||
|
group_service "code.gitea.io/gitea/services/group"
|
||||||
|
)
|
||||||
|
|
||||||
|
const tplGroupNew = "group/create"
|
||||||
|
|
||||||
|
func NewGroup(ctx *context.Context) {
|
||||||
|
ctx.Data["Title"] = ctx.Org.Organization.FullName
|
||||||
|
ctx.Data["PageIsNewGroup"] = true
|
||||||
|
if ctx.RepoGroup.Group != nil {
|
||||||
|
ctx.Data["Group"] = &group_model.Group{ParentGroupID: ctx.RepoGroup.Group.ID}
|
||||||
|
} else {
|
||||||
|
ctx.Data["Group"] = &group_model.Group{}
|
||||||
|
}
|
||||||
|
ctx.Data["Units"] = unit_model.Units
|
||||||
|
if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
|
||||||
|
ctx.ServerError("LoadHeaderCount", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := group_model.FindGroupsOptions{
|
||||||
|
ActorID: ctx.Doer.ID,
|
||||||
|
CanCreateIn: optional.Some(true),
|
||||||
|
OwnerID: ctx.Org.Organization.ID,
|
||||||
|
}
|
||||||
|
cond := group_model.AccessibleGroupCondition(ctx.Doer, unit_model.TypeInvalid, perm.AccessModeWrite)
|
||||||
|
cond = cond.And(opts.ToConds())
|
||||||
|
groups, err := group_model.FindGroupsByCond(ctx, &group_model.FindGroupsOptions{
|
||||||
|
ListOptions: db.ListOptions{
|
||||||
|
ListAll: true,
|
||||||
|
},
|
||||||
|
ParentGroupID: -1,
|
||||||
|
}, cond)
|
||||||
|
for _, g := range groups {
|
||||||
|
err = g.LoadAccessibleSubgroups(ctx, true, ctx.Doer)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("LoadAccessibleSubgroups", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("FindGroupsByCond", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Data["Groups"] = groups
|
||||||
|
ctx.HTML(http.StatusOK, tplGroupNew)
|
||||||
|
}
|
||||||
|
func NewGroupPost(ctx *context.Context) {
|
||||||
|
form := web.GetForm(ctx).(*forms.CreateGroupForm)
|
||||||
|
log.GetLogger(log.DEFAULT).Info("what? %+v", form)
|
||||||
|
g := &group_model.Group{
|
||||||
|
OwnerID: ctx.Org.Organization.ID,
|
||||||
|
Name: form.GroupName,
|
||||||
|
Description: form.Description,
|
||||||
|
OwnerName: ctx.Org.Organization.Name,
|
||||||
|
ParentGroupID: form.ParentGroupID,
|
||||||
|
}
|
||||||
|
ctx.Data["Title"] = ctx.Org.Organization.FullName
|
||||||
|
ctx.Data["PageIsGroupNew"] = true
|
||||||
|
ctx.Data["Units"] = unit_model.Units
|
||||||
|
ctx.Data["Group"] = g
|
||||||
|
|
||||||
|
if ctx.HasError() {
|
||||||
|
ctx.HTML(http.StatusOK, tplGroupNew)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := group_service.NewGroup(ctx, g); err != nil {
|
||||||
|
ctx.Data["Err_GroupName"] = true
|
||||||
|
ctx.ServerError("NewGroup", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Trace("Group created: %s/%s", ctx.Org.Organization.Name, g.Name)
|
||||||
|
ctx.Redirect(g.GroupLink())
|
||||||
|
}
|
||||||
155
routers/web/group/search.go
Normal file
155
routers/web/group/search.go
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
package group
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
group_model "code.gitea.io/gitea/models/group"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/modules/optional"
|
||||||
|
"code.gitea.io/gitea/services/context"
|
||||||
|
"code.gitea.io/gitea/services/convert"
|
||||||
|
group_service "code.gitea.io/gitea/services/group"
|
||||||
|
)
|
||||||
|
|
||||||
|
func toSearchRepoOptions(ctx *context.Context) (opts *repo_model.SearchRepoOptions) {
|
||||||
|
page := ctx.FormInt("page")
|
||||||
|
opts = &repo_model.SearchRepoOptions{
|
||||||
|
ListOptions: db.ListOptions{
|
||||||
|
Page: page,
|
||||||
|
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
|
||||||
|
},
|
||||||
|
Actor: ctx.Doer,
|
||||||
|
Keyword: ctx.FormTrim("q"),
|
||||||
|
OwnerID: ctx.FormInt64("uid"),
|
||||||
|
PriorityOwnerID: ctx.FormInt64("priority_owner_id"),
|
||||||
|
TeamID: ctx.FormInt64("team_id"),
|
||||||
|
TopicOnly: ctx.FormBool("topic"),
|
||||||
|
Collaborate: optional.None[bool](),
|
||||||
|
Private: ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private")),
|
||||||
|
Template: optional.None[bool](),
|
||||||
|
StarredByID: ctx.FormInt64("starredBy"),
|
||||||
|
IncludeDescription: ctx.FormBool("includeDesc"),
|
||||||
|
}
|
||||||
|
if ctx.FormString("template") != "" {
|
||||||
|
opts.Template = optional.Some(ctx.FormBool("template"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.FormBool("exclusive") {
|
||||||
|
opts.Collaborate = optional.Some(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
mode := ctx.FormString("mode")
|
||||||
|
switch mode {
|
||||||
|
case "source":
|
||||||
|
opts.Fork = optional.Some(false)
|
||||||
|
opts.Mirror = optional.Some(false)
|
||||||
|
case "fork":
|
||||||
|
opts.Fork = optional.Some(true)
|
||||||
|
case "mirror":
|
||||||
|
opts.Mirror = optional.Some(true)
|
||||||
|
case "collaborative":
|
||||||
|
opts.Mirror = optional.Some(false)
|
||||||
|
opts.Collaborate = optional.Some(true)
|
||||||
|
case "":
|
||||||
|
default:
|
||||||
|
estr := fmt.Sprintf("Invalid search mode: \"%s\"", mode)
|
||||||
|
ctx.Status(http.StatusUnprocessableEntity)
|
||||||
|
ctx.ServerError("toSearchRepoOptions", errors.New(estr))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.FormString("archived") != "" {
|
||||||
|
opts.Archived = optional.Some(ctx.FormBool("archived"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.FormString("is_private") != "" {
|
||||||
|
opts.IsPrivate = optional.Some(ctx.FormBool("is_private"))
|
||||||
|
}
|
||||||
|
|
||||||
|
sortMode := ctx.FormString("sort")
|
||||||
|
if len(sortMode) > 0 {
|
||||||
|
sortOrder := ctx.FormString("order")
|
||||||
|
if len(sortOrder) == 0 {
|
||||||
|
sortOrder = "asc"
|
||||||
|
}
|
||||||
|
if searchModeMap, ok := repo_model.OrderByMap[sortOrder]; ok {
|
||||||
|
if orderBy, ok := searchModeMap[sortMode]; ok {
|
||||||
|
opts.OrderBy = orderBy
|
||||||
|
} else {
|
||||||
|
estr := fmt.Errorf("Invalid sort mode: \"%s\"", sortMode)
|
||||||
|
ctx.Status(http.StatusUnprocessableEntity)
|
||||||
|
ctx.ServerError("toSearchRepoOptions", estr)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
estr := fmt.Errorf("Invalid sort order: \"%s\"", sortOrder)
|
||||||
|
ctx.Status(http.StatusUnprocessableEntity)
|
||||||
|
ctx.ServerError("toSearchRepoOptions", estr)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func SearchGroup(ctx *context.Context) {
|
||||||
|
gid := ctx.FormInt64("group_id")
|
||||||
|
var (
|
||||||
|
group *group_model.Group
|
||||||
|
err error
|
||||||
|
canSee bool = true
|
||||||
|
oid int64 = ctx.FormInt64("uid")
|
||||||
|
)
|
||||||
|
if gid > 0 {
|
||||||
|
group, err = group_model.GetGroupByID(ctx, gid)
|
||||||
|
if err != nil && !group_model.IsErrGroupNotExist(err) {
|
||||||
|
ctx.ServerError("GetGroupByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if group != nil {
|
||||||
|
canSee, err = group.CanAccess(ctx, ctx.Doer)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GroupCanAccess", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
oid = group.OwnerID
|
||||||
|
}
|
||||||
|
if !canSee {
|
||||||
|
ctx.NotFound(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
subgroupOpts := &group_model.FindGroupsOptions{
|
||||||
|
ParentGroupID: gid,
|
||||||
|
ActorID: ctx.Doer.ID,
|
||||||
|
OwnerID: oid,
|
||||||
|
}
|
||||||
|
if gid == 0 {
|
||||||
|
page := ctx.FormInt("page")
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
subgroupOpts.ListOptions = db.ListOptions{
|
||||||
|
Page: page,
|
||||||
|
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sro := toSearchRepoOptions(ctx)
|
||||||
|
rgw, err := group_service.SearchRepoGroupWeb(group, &group_service.WebSearchOptions{
|
||||||
|
OrgID: oid,
|
||||||
|
GroupOpts: subgroupOpts,
|
||||||
|
RepoOpts: *sro,
|
||||||
|
Actor: ctx.Doer,
|
||||||
|
Recurse: ctx.FormBool("recurse"),
|
||||||
|
Ctx: ctx,
|
||||||
|
Locale: ctx.Locale,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("SearchRepoGroupWeb", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.JSON(http.StatusOK, rgw)
|
||||||
|
}
|
||||||
143
routers/web/group/setting.go
Normal file
143
routers/web/group/setting.go
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package group
|
||||||
|
|
||||||
|
import (
|
||||||
|
group_model "code.gitea.io/gitea/models/group"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/optional"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/templates"
|
||||||
|
"code.gitea.io/gitea/modules/typesniffer"
|
||||||
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
shared_group "code.gitea.io/gitea/routers/web/shared/group"
|
||||||
|
"code.gitea.io/gitea/services/context"
|
||||||
|
"code.gitea.io/gitea/services/forms"
|
||||||
|
group_service "code.gitea.io/gitea/services/group"
|
||||||
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
|
user_service "code.gitea.io/gitea/services/user"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tplSettingsOptions templates.TplName = "group/settings/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RedirectToDefaultSetting(ctx *context.Context) {
|
||||||
|
ctx.Redirect(ctx.RepoGroup.OrgGroupLink + "/settings/actions/runners")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings render the main settings page
|
||||||
|
func Settings(ctx *context.Context) {
|
||||||
|
ctx.Data["Title"] = ctx.Tr("group.settings")
|
||||||
|
ctx.Data["PageIsGroupSettings"] = true
|
||||||
|
ctx.Data["PageIsSettingsOptions"] = true
|
||||||
|
ctx.Data["CurrentVisibility"] = ctx.RepoGroup.Group.Visibility
|
||||||
|
ctx.Data["ContextUser"] = ctx.ContextUser
|
||||||
|
|
||||||
|
err := shared_group.LoadHeaderCount(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("LoadHeaderCount", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.HTML(http.StatusOK, tplSettingsOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SettingsPost(ctx *context.Context) {
|
||||||
|
form := web.GetForm(ctx).(*forms.UpdateGroupSettingForm)
|
||||||
|
ctx.Data["Title"] = ctx.Tr("group.settings")
|
||||||
|
ctx.Data["PageIsGroupSettings"] = true
|
||||||
|
ctx.Data["PageIsSettingsOptions"] = true
|
||||||
|
|
||||||
|
if ctx.HasError() {
|
||||||
|
ctx.HTML(http.StatusOK, tplSettingsOptions)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
group := ctx.RepoGroup.Group
|
||||||
|
|
||||||
|
opts := &group_service.UpdateOptions{
|
||||||
|
Description: optional.Some(form.Description),
|
||||||
|
Visibility: optional.Some(form.Visibility),
|
||||||
|
}
|
||||||
|
if form.Name != group.Name {
|
||||||
|
opts.Name = optional.Some(form.Name)
|
||||||
|
}
|
||||||
|
visibilityChanged := group.Visibility != form.Visibility
|
||||||
|
if err := group_service.UpdateGroup(ctx, group, opts); err != nil {
|
||||||
|
ctx.ServerError("UpdateGroup", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if visibilityChanged {
|
||||||
|
repos, _, err := repo_model.SearchRepository(ctx, repo_model.SearchRepoOptions{
|
||||||
|
Actor: ctx.ContextUser,
|
||||||
|
Private: true,
|
||||||
|
GroupID: group.ID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("SearchRepositories", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, repo := range repos {
|
||||||
|
if err = repo_service.UpdateRepository(ctx, repo, true); err != nil {
|
||||||
|
ctx.ServerError("UpdateRepository", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Trace("Group setting updated: '%s'", group.Name)
|
||||||
|
ctx.Flash.Success(ctx.Tr("group.settings.update_setting_success"))
|
||||||
|
ctx.Redirect(ctx.RepoGroup.OrgGroupLink + "/settings")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SettingsAvatar response for change avatar on settings page
|
||||||
|
func SettingsAvatar(ctx *context.Context) {
|
||||||
|
form := web.GetForm(ctx).(*forms.AvatarForm)
|
||||||
|
form.Source = forms.AvatarLocal
|
||||||
|
if err := updateAvatarSetting(ctx, form, ctx.RepoGroup.Group); err != nil {
|
||||||
|
ctx.Flash.Error(err.Error())
|
||||||
|
} else {
|
||||||
|
ctx.Flash.Success(ctx.Tr("group.settings.update_avatar_success"))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Redirect(ctx.Org.OrgLink + "/settings")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SettingsDeleteAvatar response for delete avatar on settings page
|
||||||
|
func SettingsDeleteAvatar(ctx *context.Context) {
|
||||||
|
if err := user_service.DeleteAvatar(ctx, ctx.Org.Organization.AsUser()); err != nil {
|
||||||
|
ctx.Flash.Error(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSONRedirect(ctx.RepoGroup.OrgGroupLink + "/settings")
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateAvatarSetting(ctx *context.Context, form *forms.AvatarForm, group *group_model.Group) error {
|
||||||
|
if form.Avatar != nil && form.Avatar.Filename != "" {
|
||||||
|
fr, err := form.Avatar.Open()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Avatar.Open: %w", err)
|
||||||
|
}
|
||||||
|
defer fr.Close()
|
||||||
|
|
||||||
|
if form.Avatar.Size > setting.Avatar.MaxFileSize {
|
||||||
|
return errors.New(ctx.Locale.TrString("settings.uploaded_avatar_is_too_big", form.Avatar.Size/1024, setting.Avatar.MaxFileSize/1024))
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := io.ReadAll(fr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("io.ReadAll: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
st := typesniffer.DetectContentType(data)
|
||||||
|
if !(st.IsImage() && !st.IsSvgImage()) {
|
||||||
|
return errors.New(ctx.Locale.TrString("settings.uploaded_avatar_not_a_image"))
|
||||||
|
}
|
||||||
|
if err = group_service.UploadAvatar(ctx, group, data); err != nil {
|
||||||
|
return fmt.Errorf("UploadAvatar: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
216
routers/web/group/team.go
Normal file
216
routers/web/group/team.go
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
package group
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
group_model "code.gitea.io/gitea/models/group"
|
||||||
|
org_model "code.gitea.io/gitea/models/organization"
|
||||||
|
"code.gitea.io/gitea/models/perm"
|
||||||
|
unit_model "code.gitea.io/gitea/models/unit"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
web_org "code.gitea.io/gitea/routers/web/org"
|
||||||
|
shared_group "code.gitea.io/gitea/routers/web/shared/group"
|
||||||
|
"code.gitea.io/gitea/services/context"
|
||||||
|
"code.gitea.io/gitea/services/convert"
|
||||||
|
"code.gitea.io/gitea/services/forms"
|
||||||
|
group_service "code.gitea.io/gitea/services/group"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tplTeamEdit = "group/team/new"
|
||||||
|
tplTeams = "group/team/teams"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MinUnitAccessMode(unitsMap map[unit_model.Type]perm.AccessMode) perm.AccessMode {
|
||||||
|
res := perm.AccessModeNone
|
||||||
|
for t, mode := range unitsMap {
|
||||||
|
// Don't allow `TypeExternal{Tracker,Wiki}` to influence this as they can only be set to READ perms.
|
||||||
|
if t == unit_model.TypeExternalTracker || t == unit_model.TypeExternalWiki {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the minial permission great than AccessModeNone except all are AccessModeNone
|
||||||
|
if mode > perm.AccessModeNone && (res == perm.AccessModeNone || mode < res) {
|
||||||
|
res = mode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func SearchTeamCandidates(ctx *context.Context) {
|
||||||
|
teams, _, err := org_model.SearchTeam(ctx, &org_model.SearchTeamOptions{
|
||||||
|
OrgID: ctx.Org.Organization.ID,
|
||||||
|
Keyword: ctx.FormTrim("q"),
|
||||||
|
ListOptions: db.ListOptions{
|
||||||
|
PageSize: setting.UI.MembersPagingNum,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("Unable to search teams", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
apiTeams, err := convert.ToTeams(ctx, teams, true)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("Unable to search teams", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.JSON(http.StatusOK, map[string]any{"data": apiTeams})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Teams(ctx *context.Context) {
|
||||||
|
group := ctx.RepoGroup.Group
|
||||||
|
ctx.Data["Title"] = group.Name
|
||||||
|
ctx.Data["PageIsGroupTeams"] = true
|
||||||
|
for _, t := range ctx.RepoGroup.Teams {
|
||||||
|
if err := t.LoadMembers(ctx); err != nil {
|
||||||
|
ctx.ServerError("GetMembers", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.Data["Teams"] = ctx.RepoGroup.Teams
|
||||||
|
|
||||||
|
err := shared_group.LoadHeaderCount(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("RenderOrgHeader", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.HTML(http.StatusOK, tplTeams)
|
||||||
|
}
|
||||||
|
|
||||||
|
func EditTeam(ctx *context.Context) {
|
||||||
|
ctx.Data["Title"] = ctx.RepoGroup.Group.Name
|
||||||
|
ctx.Data["PageIsGroupTeams"] = true
|
||||||
|
if err := ctx.RepoGroup.Team.LoadUnits(ctx); err != nil {
|
||||||
|
ctx.ServerError("LoadUnits", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := shared_group.LoadHeaderCount(ctx); err != nil {
|
||||||
|
ctx.ServerError("LoadHeaderCount", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Data["GroupTeam"] = ctx.RepoGroup.GroupTeam
|
||||||
|
ctx.Data["Team"] = ctx.RepoGroup.Team
|
||||||
|
ctx.Data["Units"] = unit_model.Units
|
||||||
|
ctx.HTML(http.StatusOK, tplTeamEdit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func EditTeamPost(ctx *context.Context) {
|
||||||
|
form := web.GetForm(ctx).(*forms.CreateGroupTeamForm)
|
||||||
|
t := ctx.RepoGroup.Team
|
||||||
|
gt := ctx.RepoGroup.GroupTeam
|
||||||
|
newAccessMode := perm.ParseAccessMode(form.Permission)
|
||||||
|
unitPerms := web_org.GetUnitPerms(ctx.Req.Form, newAccessMode)
|
||||||
|
if newAccessMode < perm.AccessModeAdmin {
|
||||||
|
newAccessMode = MinUnitAccessMode(unitPerms)
|
||||||
|
}
|
||||||
|
ctx.Data["Title"] = ctx.RepoGroup.Group.Name
|
||||||
|
ctx.Data["PageIsGroupTeams"] = true
|
||||||
|
ctx.Data["Team"] = t
|
||||||
|
ctx.Data["GroupTeam"] = gt
|
||||||
|
ctx.Data["Units"] = unit_model.Units
|
||||||
|
if !t.IsOwnerTeam() {
|
||||||
|
if gt.AccessMode != newAccessMode {
|
||||||
|
gt.AccessMode = newAccessMode
|
||||||
|
}
|
||||||
|
if gt.CanCreateIn != form.CanCreateRepoOrSubGroup {
|
||||||
|
gt.CanCreateIn = form.CanCreateRepoOrSubGroup
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gt.CanCreateIn = true
|
||||||
|
}
|
||||||
|
units := make([]*group_model.RepoGroupUnit, 0, len(unitPerms))
|
||||||
|
for tp, perm := range unitPerms {
|
||||||
|
units = append(units, &group_model.RepoGroupUnit{
|
||||||
|
GroupID: gt.GroupID,
|
||||||
|
TeamID: t.ID,
|
||||||
|
Type: tp,
|
||||||
|
AccessMode: perm,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
gt.Units = units
|
||||||
|
if ctx.HasError() {
|
||||||
|
ctx.HTML(http.StatusOK, tplTeamEdit)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if gt.AccessMode < perm.AccessModeAdmin && len(unitPerms) == 0 {
|
||||||
|
ctx.RenderWithErr(ctx.Tr("form.team_no_units_error"), tplTeamEdit, &form)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := group_service.UpdateGroupTeam(ctx, gt); err != nil {
|
||||||
|
ctx.ServerError("UpdateGroupTeam", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Redirect(ctx.Org.OrgLink + "/teams/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TeamAddPost(ctx *context.Context) {
|
||||||
|
if !ctx.RepoGroup.IsGroupAdmin || !ctx.RepoGroup.IsOwner {
|
||||||
|
ctx.NotFound(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
group := ctx.RepoGroup.Group
|
||||||
|
tname := strings.ToLower(ctx.FormTrim("tname"))
|
||||||
|
t, err := org_model.GetTeam(ctx, group.OwnerID, tname)
|
||||||
|
if err != nil {
|
||||||
|
if org_model.IsErrTeamNotExist(err) {
|
||||||
|
ctx.Flash.Error(ctx.Tr("form.team_not_exist"))
|
||||||
|
ctx.Redirect(ctx.RepoGroup.OrgGroupLink + "/teams")
|
||||||
|
} else {
|
||||||
|
ctx.ServerError("GetTeam", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
has := group_model.HasTeamGroup(ctx, group.OwnerID, t.ID, group.ID)
|
||||||
|
if has {
|
||||||
|
ctx.Flash.Error(ctx.Tr("org.group.add_duplicate_team"))
|
||||||
|
} else {
|
||||||
|
parentGroup, err := group_model.FindGroupTeamByTeamID(ctx, group.ID, t.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("FindGroupTeamByTeamID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mode := t.AccessMode
|
||||||
|
canCreateIn := t.CanCreateOrgRepo
|
||||||
|
if parentGroup != nil {
|
||||||
|
mode = max(t.AccessMode, parentGroup.AccessMode)
|
||||||
|
canCreateIn = parentGroup.CanCreateIn || t.CanCreateOrgRepo
|
||||||
|
}
|
||||||
|
if err = group.LoadParentGroup(ctx); err != nil {
|
||||||
|
ctx.ServerError("LoadParentGroup", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = group_model.AddTeamGroup(ctx, ctx.RepoGroup.Group.OwnerID, t.ID, ctx.RepoGroup.Group.ID, mode, canCreateIn)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("AddTeamGroup", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.Redirect(group.OrgGroupLink() + "/teams")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TeamRemove(ctx *context.Context) {
|
||||||
|
if !ctx.RepoGroup.IsGroupAdmin || !ctx.RepoGroup.IsOwner {
|
||||||
|
ctx.NotFound(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
org := ctx.Org.Organization.ID
|
||||||
|
group := ctx.RepoGroup.Group
|
||||||
|
team, err := org_model.GetTeam(ctx, org, ctx.PathParam("team"))
|
||||||
|
if err != nil {
|
||||||
|
if org_model.IsErrTeamNotExist(err) {
|
||||||
|
ctx.NotFound(err)
|
||||||
|
} else {
|
||||||
|
ctx.ServerError("GetTeam", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = group_model.RemoveTeamGroup(ctx, org, team.ID, group.ID); err != nil {
|
||||||
|
ctx.ServerError("RemoveTeamGroup", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.JSONRedirect(ctx.RepoGroup.OrgGroupLink + "/teams")
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@
|
|||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"code.gitea.io/gitea/routers/web/group"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -922,6 +923,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
|||||||
|
|
||||||
m.Group("/org", func() {
|
m.Group("/org", func() {
|
||||||
m.Group("/{org}", func() {
|
m.Group("/{org}", func() {
|
||||||
|
m.Get("/-/search_team_candidates", optExploreSignIn, group.SearchTeamCandidates)
|
||||||
m.Get("/members", org.Members)
|
m.Get("/members", org.Members)
|
||||||
}, context.OrgAssignment(context.OrgAssignmentOptions{}))
|
}, context.OrgAssignment(context.OrgAssignmentOptions{}))
|
||||||
}, optSignIn)
|
}, optSignIn)
|
||||||
@ -951,6 +953,31 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
|||||||
m.Get("/milestones/{team}", reqMilestonesDashboardPageEnabled, user.Milestones)
|
m.Get("/milestones/{team}", reqMilestonesDashboardPageEnabled, user.Milestones)
|
||||||
m.Post("/members/action/{action}", org.MembersAction)
|
m.Post("/members/action/{action}", org.MembersAction)
|
||||||
m.Get("/teams", org.Teams)
|
m.Get("/teams", org.Teams)
|
||||||
|
|
||||||
|
m.Group("/groups", func() {
|
||||||
|
m.Combo("/new").
|
||||||
|
Get(group.NewGroup).
|
||||||
|
Post(web.Bind(forms.CreateGroupForm{}), group.NewGroupPost)
|
||||||
|
m.Group("/{group_id}", func() {
|
||||||
|
m.Get("/teams", group.Teams)
|
||||||
|
m.Post("/teams/add", group.TeamAddPost)
|
||||||
|
m.Combo("/teams/{team}/edit").
|
||||||
|
Get(group.EditTeam).
|
||||||
|
Post(web.Bind(forms.CreateGroupTeamForm{}), group.EditTeamPost)
|
||||||
|
m.Post("/teams/{team}/remove", group.TeamRemove)
|
||||||
|
m.Group("/settings", func() {
|
||||||
|
m.Combo("").
|
||||||
|
Get(group.Settings).
|
||||||
|
Post(web.Bind(forms.UpdateGroupSettingForm{}), group.SettingsPost)
|
||||||
|
m.Post("/avatar", web.Bind(forms.AvatarForm{}), group.SettingsAvatar)
|
||||||
|
m.Post("/avatar/delete", group.SettingsDeleteAvatar)
|
||||||
|
}, ctxDataSet("PageIsGroupSettings", true))
|
||||||
|
}, context.GroupAssignment(context.GroupAssignmentOptions{
|
||||||
|
RequireMember: true,
|
||||||
|
RequireOwner: false,
|
||||||
|
RequireGroupAdmin: true,
|
||||||
|
}))
|
||||||
|
})
|
||||||
}, context.OrgAssignment(context.OrgAssignmentOptions{RequireMember: true, RequireTeamMember: true}))
|
}, context.OrgAssignment(context.OrgAssignmentOptions{RequireMember: true, RequireTeamMember: true}))
|
||||||
|
|
||||||
m.Group("/{org}", func() {
|
m.Group("/{org}", func() {
|
||||||
@ -1053,6 +1080,11 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
|||||||
}, reqSignIn)
|
}, reqSignIn)
|
||||||
// end "/org": most org routes
|
// end "/org": most org routes
|
||||||
|
|
||||||
|
m.Group("/group", func() {
|
||||||
|
m.Get("/search", group.SearchGroup)
|
||||||
|
}, reqSignIn)
|
||||||
|
// end "/group": search
|
||||||
|
|
||||||
m.Group("/repo", func() {
|
m.Group("/repo", func() {
|
||||||
m.Get("/create", repo.Create)
|
m.Get("/create", repo.Create)
|
||||||
m.Post("/create", web.Bind(forms.CreateRepoForm{}), repo.CreatePost)
|
m.Post("/create", web.Bind(forms.CreateRepoForm{}), repo.CreatePost)
|
||||||
@ -1126,6 +1158,18 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
|||||||
}, reqUnitAccess(unit.TypeCode, perm.AccessModeRead, false), individualPermsChecker)
|
}, reqUnitAccess(unit.TypeCode, perm.AccessModeRead, false), individualPermsChecker)
|
||||||
}, optSignIn, context.UserAssignmentWeb(), context.OrgAssignment(context.OrgAssignmentOptions{}))
|
}, optSignIn, context.UserAssignmentWeb(), context.OrgAssignment(context.OrgAssignmentOptions{}))
|
||||||
// end "/{username}/-": packages, projects, code
|
// end "/{username}/-": packages, projects, code
|
||||||
|
m.Group("/{username}/groups", func() {
|
||||||
|
m.Group("/{group_id}", func() {
|
||||||
|
m.Get("", group.Home)
|
||||||
|
}, context.GroupAssignment(context.GroupAssignmentOptions{}))
|
||||||
|
}, optSignIn, context.UserAssignmentWeb(), context.OrgAssignment(context.OrgAssignmentOptions{}))
|
||||||
|
|
||||||
|
m.Group("/{username}/groups", func() {
|
||||||
|
m.Post("/items/move", group.MoveGroupItem)
|
||||||
|
}, context.UserAssignmentWeb(), context.OrgAssignment(context.OrgAssignmentOptions{
|
||||||
|
RequireMember: true,
|
||||||
|
}))
|
||||||
|
// end "/{username}/groups"
|
||||||
|
|
||||||
repoDashFn := func() {
|
repoDashFn := func() {
|
||||||
m.Group("/migrate", func() {
|
m.Group("/migrate", func() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user