mirror of
https://github.com/go-gitea/gitea.git
synced 2025-10-26 08:26:22 +01:00
Support actions and reusable workflows from private repos (#32562)
Resolve https://gitea.com/gitea/act_runner/issues/102 This PR allows administrators of a private repository to specify some collaborative owners. The repositories of collaborative owners will be allowed to access this repository's actions and workflows. Settings for private repos:  --- This PR also moves "Enable Actions" setting to `Actions > General` page <img width="960" alt="image" src="https://github.com/user-attachments/assets/49337ec2-afb1-4a67-8516-5c9ef0ce05d4" /> <img width="960" alt="image" src="https://github.com/user-attachments/assets/f58ee6d5-17f9-4180-8760-a78e859f1c37" /> --------- Signed-off-by: Zettat123 <zettat123@gmail.com> Co-authored-by: ChristopherHX <christopher.homberger@web.de>
This commit is contained in:
@@ -139,3 +139,23 @@
|
|||||||
updated: 1683636626
|
updated: 1683636626
|
||||||
need_approval: 0
|
need_approval: 0
|
||||||
approved_by: 0
|
approved_by: 0
|
||||||
|
-
|
||||||
|
id: 804
|
||||||
|
title: "use a private action"
|
||||||
|
repo_id: 60
|
||||||
|
owner_id: 40
|
||||||
|
workflow_id: "run.yaml"
|
||||||
|
index: 189
|
||||||
|
trigger_user_id: 40
|
||||||
|
ref: "refs/heads/master"
|
||||||
|
commit_sha: "6e64b26de7ba966d01d90ecfaf5c7f14ef203e86"
|
||||||
|
event: "push"
|
||||||
|
trigger_event: "push"
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
status: 1
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
created: 1683636108
|
||||||
|
updated: 1683636626
|
||||||
|
need_approval: 0
|
||||||
|
approved_by: 0
|
||||||
|
|||||||
@@ -129,3 +129,17 @@
|
|||||||
status: 5
|
status: 5
|
||||||
started: 1683636528
|
started: 1683636528
|
||||||
stopped: 1683636626
|
stopped: 1683636626
|
||||||
|
-
|
||||||
|
id: 205
|
||||||
|
run_id: 804
|
||||||
|
repo_id: 6
|
||||||
|
owner_id: 10
|
||||||
|
commit_sha: 6e64b26de7ba966d01d90ecfaf5c7f14ef203e86
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
name: job_2
|
||||||
|
attempt: 1
|
||||||
|
job_id: job_2
|
||||||
|
task_id: 48
|
||||||
|
status: 1
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
|||||||
@@ -177,3 +177,23 @@
|
|||||||
log_length: 0
|
log_length: 0
|
||||||
log_size: 0
|
log_size: 0
|
||||||
log_expired: 0
|
log_expired: 0
|
||||||
|
-
|
||||||
|
id: 55
|
||||||
|
job_id: 205
|
||||||
|
attempt: 1
|
||||||
|
runner_id: 1
|
||||||
|
status: 6 # 6 is the status code for "running"
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
repo_id: 6
|
||||||
|
owner_id: 10
|
||||||
|
commit_sha: 6e64b26de7ba966d01d90ecfaf5c7f14ef203e86
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc478422b
|
||||||
|
token_salt: ERxJGHvg3I
|
||||||
|
token_last_eight: 182199eb
|
||||||
|
log_filename: collaborative-owner-test/1a/49.log
|
||||||
|
log_in_storage: 1
|
||||||
|
log_length: 707
|
||||||
|
log_size: 90179
|
||||||
|
log_expired: 0
|
||||||
|
|||||||
@@ -733,3 +733,10 @@
|
|||||||
type: 3
|
type: 3
|
||||||
config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}"
|
config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}"
|
||||||
created_unix: 946684810
|
created_unix: 946684810
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 111
|
||||||
|
repo_id: 3
|
||||||
|
type: 10
|
||||||
|
config: "{}"
|
||||||
|
created_unix: 946684810
|
||||||
|
|||||||
@@ -264,13 +264,22 @@ func GetActionsUserRepoPermission(ctx context.Context, repo *repo_model.Reposito
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return perm, err
|
return perm, err
|
||||||
}
|
}
|
||||||
if task.RepoID != repo.ID {
|
|
||||||
// FIXME allow public repo read access if tokenless pull is enabled
|
|
||||||
return perm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var accessMode perm_model.AccessMode
|
var accessMode perm_model.AccessMode
|
||||||
if task.IsForkPullRequest {
|
if task.RepoID != repo.ID {
|
||||||
|
taskRepo, exist, err := db.GetByID[repo_model.Repository](ctx, task.RepoID)
|
||||||
|
if err != nil || !exist {
|
||||||
|
return perm, err
|
||||||
|
}
|
||||||
|
actionsCfg := repo.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
|
||||||
|
if !actionsCfg.IsCollaborativeOwner(taskRepo.OwnerID) || !taskRepo.IsPrivate {
|
||||||
|
// The task repo can access the current repo only if the task repo is private and
|
||||||
|
// the owner of the task repo is a collaborative owner of the current repo.
|
||||||
|
// FIXME allow public repo read access if tokenless pull is enabled
|
||||||
|
return perm, nil
|
||||||
|
}
|
||||||
|
accessMode = perm_model.AccessModeRead
|
||||||
|
} else if task.IsForkPullRequest {
|
||||||
accessMode = perm_model.AccessModeRead
|
accessMode = perm_model.AccessModeRead
|
||||||
} else {
|
} else {
|
||||||
accessMode = perm_model.AccessModeWrite
|
accessMode = perm_model.AccessModeWrite
|
||||||
|
|||||||
@@ -170,6 +170,9 @@ func (cfg *PullRequestsConfig) GetDefaultMergeStyle() MergeStyle {
|
|||||||
|
|
||||||
type ActionsConfig struct {
|
type ActionsConfig struct {
|
||||||
DisabledWorkflows []string
|
DisabledWorkflows []string
|
||||||
|
// CollaborativeOwnerIDs is a list of owner IDs used to share actions from private repos.
|
||||||
|
// Only workflows from the private repos whose owners are in CollaborativeOwnerIDs can access the current repo's actions.
|
||||||
|
CollaborativeOwnerIDs []int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *ActionsConfig) EnableWorkflow(file string) {
|
func (cfg *ActionsConfig) EnableWorkflow(file string) {
|
||||||
@@ -192,6 +195,20 @@ func (cfg *ActionsConfig) DisableWorkflow(file string) {
|
|||||||
cfg.DisabledWorkflows = append(cfg.DisabledWorkflows, file)
|
cfg.DisabledWorkflows = append(cfg.DisabledWorkflows, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg *ActionsConfig) AddCollaborativeOwner(ownerID int64) {
|
||||||
|
if !slices.Contains(cfg.CollaborativeOwnerIDs, ownerID) {
|
||||||
|
cfg.CollaborativeOwnerIDs = append(cfg.CollaborativeOwnerIDs, ownerID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *ActionsConfig) RemoveCollaborativeOwner(ownerID int64) {
|
||||||
|
cfg.CollaborativeOwnerIDs = util.SliceRemoveAll(cfg.CollaborativeOwnerIDs, ownerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *ActionsConfig) IsCollaborativeOwner(ownerID int64) bool {
|
||||||
|
return slices.Contains(cfg.CollaborativeOwnerIDs, ownerID)
|
||||||
|
}
|
||||||
|
|
||||||
// FromDB fills up a ActionsConfig from serialized format.
|
// FromDB fills up a ActionsConfig from serialized format.
|
||||||
func (cfg *ActionsConfig) FromDB(bs []byte) error {
|
func (cfg *ActionsConfig) FromDB(bs []byte) error {
|
||||||
return json.UnmarshalHandleDoubleEncode(bs, &cfg)
|
return json.UnmarshalHandleDoubleEncode(bs, &cfg)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package user
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
@@ -22,7 +23,7 @@ type SearchUserOptions struct {
|
|||||||
db.ListOptions
|
db.ListOptions
|
||||||
|
|
||||||
Keyword string
|
Keyword string
|
||||||
Type UserType
|
Types []UserType
|
||||||
UID int64
|
UID int64
|
||||||
LoginName string // this option should be used only for admin user
|
LoginName string // this option should be used only for admin user
|
||||||
SourceID int64 // this option should be used only for admin user
|
SourceID int64 // this option should be used only for admin user
|
||||||
@@ -43,16 +44,16 @@ type SearchUserOptions struct {
|
|||||||
|
|
||||||
func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Session {
|
func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Session {
|
||||||
var cond builder.Cond
|
var cond builder.Cond
|
||||||
cond = builder.Eq{"type": opts.Type}
|
cond = builder.In("type", opts.Types)
|
||||||
if opts.IncludeReserved {
|
if opts.IncludeReserved {
|
||||||
switch opts.Type {
|
switch {
|
||||||
case UserTypeIndividual:
|
case slices.Contains(opts.Types, UserTypeIndividual):
|
||||||
cond = cond.Or(builder.Eq{"type": UserTypeUserReserved}).Or(
|
cond = cond.Or(builder.Eq{"type": UserTypeUserReserved}).Or(
|
||||||
builder.Eq{"type": UserTypeBot},
|
builder.Eq{"type": UserTypeBot},
|
||||||
).Or(
|
).Or(
|
||||||
builder.Eq{"type": UserTypeRemoteUser},
|
builder.Eq{"type": UserTypeRemoteUser},
|
||||||
)
|
)
|
||||||
case UserTypeOrganization:
|
case slices.Contains(opts.Types, UserTypeOrganization):
|
||||||
cond = cond.Or(builder.Eq{"type": UserTypeOrganizationReserved})
|
cond = cond.Or(builder.Eq{"type": UserTypeOrganizationReserved})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1449,3 +1449,15 @@ func DisabledFeaturesWithLoginType(user *User) *container.Set[string] {
|
|||||||
}
|
}
|
||||||
return &setting.Admin.UserDisabledFeatures
|
return &setting.Admin.UserDisabledFeatures
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserOrOrgIDByName returns the id for a user or an org by name
|
||||||
|
func GetUserOrOrgIDByName(ctx context.Context, name string) (int64, error) {
|
||||||
|
var id int64
|
||||||
|
has, err := db.GetEngine(ctx).Table("user").Where("name = ?", name).Cols("id").Get(&id)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else if !has {
|
||||||
|
return 0, fmt.Errorf("user or org with name %s: %w", name, util.ErrNotExist)
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ func TestSearchUsers(t *testing.T) {
|
|||||||
|
|
||||||
// test orgs
|
// test orgs
|
||||||
testOrgSuccess := func(opts user_model.SearchUserOptions, expectedOrgIDs []int64) {
|
testOrgSuccess := func(opts user_model.SearchUserOptions, expectedOrgIDs []int64) {
|
||||||
opts.Type = user_model.UserTypeOrganization
|
opts.Types = []user_model.UserType{user_model.UserTypeOrganization}
|
||||||
testSuccess(opts, expectedOrgIDs)
|
testSuccess(opts, expectedOrgIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ func TestSearchUsers(t *testing.T) {
|
|||||||
|
|
||||||
// test users
|
// test users
|
||||||
testUserSuccess := func(opts user_model.SearchUserOptions, expectedUserIDs []int64) {
|
testUserSuccess := func(opts user_model.SearchUserOptions, expectedUserIDs []int64) {
|
||||||
opts.Type = user_model.UserTypeIndividual
|
opts.Types = []user_model.UserType{user_model.UserTypeIndividual}
|
||||||
testSuccess(opts, expectedUserIDs)
|
testSuccess(opts, expectedUserIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3914,6 +3914,15 @@ variables.update.success = The variable has been edited.
|
|||||||
logs.always_auto_scroll = Always auto scroll logs
|
logs.always_auto_scroll = Always auto scroll logs
|
||||||
logs.always_expand_running = Always expand running logs
|
logs.always_expand_running = Always expand running logs
|
||||||
|
|
||||||
|
general = General
|
||||||
|
general.enable_actions = Enable Actions
|
||||||
|
general.collaborative_owners_management = Collaborative Owners Management
|
||||||
|
general.collaborative_owners_management_help = A collaborative owner is a user or an organization whose private repository has access to the actions and workflows of this repository.
|
||||||
|
general.add_collaborative_owner = Add Collaborative Owner
|
||||||
|
general.collaborative_owner_not_exist = The collaborative owner does not exist.
|
||||||
|
general.remove_collaborative_owner = Remove Collaborative Owner
|
||||||
|
general.remove_collaborative_owner_desc = Removing a collaborative owner will prevent the repositories of the owner from accessing the actions in this repository. Continue?
|
||||||
|
|
||||||
[projects]
|
[projects]
|
||||||
deleted.display_name = Deleted Project
|
deleted.display_name = Deleted Project
|
||||||
type-1.display_name = Individual Project
|
type-1.display_name = Individual Project
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ func GetAllOrgs(ctx *context.APIContext) {
|
|||||||
|
|
||||||
users, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
|
users, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
|
||||||
Actor: ctx.Doer,
|
Actor: ctx.Doer,
|
||||||
Type: user_model.UserTypeOrganization,
|
Types: []user_model.UserType{user_model.UserTypeOrganization},
|
||||||
OrderBy: db.SearchOrderByAlphabetically,
|
OrderBy: db.SearchOrderByAlphabetically,
|
||||||
ListOptions: listOptions,
|
ListOptions: listOptions,
|
||||||
Visible: []api.VisibleType{api.VisibleTypePublic, api.VisibleTypeLimited, api.VisibleTypePrivate},
|
Visible: []api.VisibleType{api.VisibleTypePublic, api.VisibleTypeLimited, api.VisibleTypePrivate},
|
||||||
|
|||||||
@@ -425,7 +425,7 @@ func SearchUsers(ctx *context.APIContext) {
|
|||||||
|
|
||||||
users, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
|
users, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
|
||||||
Actor: ctx.Doer,
|
Actor: ctx.Doer,
|
||||||
Type: user_model.UserTypeIndividual,
|
Types: []user_model.UserType{user_model.UserTypeIndividual},
|
||||||
LoginName: ctx.FormTrim("login_name"),
|
LoginName: ctx.FormTrim("login_name"),
|
||||||
SourceID: ctx.FormInt64("source_id"),
|
SourceID: ctx.FormInt64("source_id"),
|
||||||
OrderBy: db.SearchOrderByAlphabetically,
|
OrderBy: db.SearchOrderByAlphabetically,
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ func GetAll(ctx *context.APIContext) {
|
|||||||
publicOrgs, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
|
publicOrgs, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
|
||||||
Actor: ctx.Doer,
|
Actor: ctx.Doer,
|
||||||
ListOptions: listOptions,
|
ListOptions: listOptions,
|
||||||
Type: user_model.UserTypeOrganization,
|
Types: []user_model.UserType{user_model.UserTypeOrganization},
|
||||||
OrderBy: db.SearchOrderByAlphabetically,
|
OrderBy: db.SearchOrderByAlphabetically,
|
||||||
Visible: vMode,
|
Visible: vMode,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ func Search(ctx *context.APIContext) {
|
|||||||
Actor: ctx.Doer,
|
Actor: ctx.Doer,
|
||||||
Keyword: ctx.FormTrim("q"),
|
Keyword: ctx.FormTrim("q"),
|
||||||
UID: uid,
|
UID: uid,
|
||||||
Type: user_model.UserTypeIndividual,
|
Types: []user_model.UserType{user_model.UserTypeIndividual},
|
||||||
SearchByEmail: true,
|
SearchByEmail: true,
|
||||||
Visible: visible,
|
Visible: visible,
|
||||||
ListOptions: listOptions,
|
ListOptions: listOptions,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func Organizations(ctx *context.Context) {
|
|||||||
|
|
||||||
explore.RenderUserSearch(ctx, user_model.SearchUserOptions{
|
explore.RenderUserSearch(ctx, user_model.SearchUserOptions{
|
||||||
Actor: ctx.Doer,
|
Actor: ctx.Doer,
|
||||||
Type: user_model.UserTypeOrganization,
|
Types: []user_model.UserType{user_model.UserTypeOrganization},
|
||||||
IncludeReserved: true, // administrator needs to list all accounts include reserved
|
IncludeReserved: true, // administrator needs to list all accounts include reserved
|
||||||
ListOptions: db.ListOptions{
|
ListOptions: db.ListOptions{
|
||||||
PageSize: setting.UI.Admin.OrgPagingNum,
|
PageSize: setting.UI.Admin.OrgPagingNum,
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ func Users(ctx *context.Context) {
|
|||||||
|
|
||||||
explore.RenderUserSearch(ctx, user_model.SearchUserOptions{
|
explore.RenderUserSearch(ctx, user_model.SearchUserOptions{
|
||||||
Actor: ctx.Doer,
|
Actor: ctx.Doer,
|
||||||
Type: user_model.UserTypeIndividual,
|
Types: []user_model.UserType{user_model.UserTypeIndividual},
|
||||||
ListOptions: db.ListOptions{
|
ListOptions: db.ListOptions{
|
||||||
PageSize: setting.UI.Admin.UserPagingNum,
|
PageSize: setting.UI.Admin.UserPagingNum,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func Organizations(ctx *context.Context) {
|
|||||||
|
|
||||||
RenderUserSearch(ctx, user_model.SearchUserOptions{
|
RenderUserSearch(ctx, user_model.SearchUserOptions{
|
||||||
Actor: ctx.Doer,
|
Actor: ctx.Doer,
|
||||||
Type: user_model.UserTypeOrganization,
|
Types: []user_model.UserType{user_model.UserTypeOrganization},
|
||||||
ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},
|
ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},
|
||||||
Visible: visibleTypes,
|
Visible: visibleTypes,
|
||||||
|
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ func Users(ctx *context.Context) {
|
|||||||
|
|
||||||
RenderUserSearch(ctx, user_model.SearchUserOptions{
|
RenderUserSearch(ctx, user_model.SearchUserOptions{
|
||||||
Actor: ctx.Doer,
|
Actor: ctx.Doer,
|
||||||
Type: user_model.UserTypeIndividual,
|
Types: []user_model.UserType{user_model.UserTypeIndividual},
|
||||||
ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},
|
ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},
|
||||||
IsActive: optional.Some(true),
|
IsActive: optional.Some(true),
|
||||||
Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate},
|
Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate},
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ func HomeSitemap(ctx *context.Context) {
|
|||||||
m := sitemap.NewSitemapIndex()
|
m := sitemap.NewSitemapIndex()
|
||||||
if !setting.Service.Explore.DisableUsersPage {
|
if !setting.Service.Explore.DisableUsersPage {
|
||||||
_, cnt, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
|
_, cnt, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
|
||||||
Type: user_model.UserTypeIndividual,
|
Types: []user_model.UserType{user_model.UserTypeIndividual},
|
||||||
ListOptions: db.ListOptions{PageSize: 1},
|
ListOptions: db.ListOptions{PageSize: 1},
|
||||||
IsActive: optional.Some(true),
|
IsActive: optional.Some(true),
|
||||||
Visible: []structs.VisibleType{structs.VisibleTypePublic},
|
Visible: []structs.VisibleType{structs.VisibleTypePublic},
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ func httpBase(ctx *context.Context) *serviceHandler {
|
|||||||
taskID := ctx.Data["ActionsTaskID"].(int64)
|
taskID := ctx.Data["ActionsTaskID"].(int64)
|
||||||
p, err := access_model.GetActionsUserRepoPermission(ctx, repo, ctx.Doer, taskID)
|
p, err := access_model.GetActionsUserRepoPermission(ctx, repo, ctx.Doer, taskID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetUserRepoPermission", err)
|
ctx.ServerError("GetActionsUserRepoPermission", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
121
routers/web/repo/setting/actions.go
Normal file
121
routers/web/repo/setting/actions.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
unit_model "code.gitea.io/gitea/models/unit"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/templates"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
"code.gitea.io/gitea/services/context"
|
||||||
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
const tplRepoActionsGeneralSettings templates.TplName = "repo/settings/actions"
|
||||||
|
|
||||||
|
func ActionsGeneralSettings(ctx *context.Context) {
|
||||||
|
ctx.Data["Title"] = ctx.Tr("actions.general")
|
||||||
|
ctx.Data["PageType"] = "general"
|
||||||
|
ctx.Data["PageIsActionsSettingsGeneral"] = true
|
||||||
|
|
||||||
|
actionsUnit, err := ctx.Repo.Repository.GetUnit(ctx, unit_model.TypeActions)
|
||||||
|
if err != nil && !repo_model.IsErrUnitTypeNotExist(err) {
|
||||||
|
ctx.ServerError("GetUnit", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if actionsUnit == nil { // no actions unit
|
||||||
|
ctx.HTML(http.StatusOK, tplRepoActionsGeneralSettings)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Repo.Repository.IsPrivate {
|
||||||
|
collaborativeOwnerIDs := actionsUnit.ActionsConfig().CollaborativeOwnerIDs
|
||||||
|
collaborativeOwners, err := user_model.GetUsersByIDs(ctx, collaborativeOwnerIDs)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetUsersByIDs", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Data["CollaborativeOwners"] = collaborativeOwners
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.HTML(http.StatusOK, tplRepoActionsGeneralSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ActionsUnitPost(ctx *context.Context) {
|
||||||
|
redirectURL := ctx.Repo.RepoLink + "/settings/actions/general"
|
||||||
|
enableActionsUnit := ctx.FormBool("enable_actions")
|
||||||
|
repo := ctx.Repo.Repository
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if enableActionsUnit && !unit_model.TypeActions.UnitGlobalDisabled() {
|
||||||
|
err = repo_service.UpdateRepositoryUnits(ctx, repo, []repo_model.RepoUnit{newRepoUnit(repo, unit_model.TypeActions, nil)}, nil)
|
||||||
|
} else if !unit_model.TypeActions.UnitGlobalDisabled() {
|
||||||
|
err = repo_service.UpdateRepositoryUnits(ctx, repo, nil, []unit_model.Type{unit_model.TypeActions})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("UpdateRepositoryUnits", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
|
||||||
|
ctx.Redirect(redirectURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddCollaborativeOwner(ctx *context.Context) {
|
||||||
|
name := strings.ToLower(ctx.FormString("collaborative_owner"))
|
||||||
|
|
||||||
|
ownerID, err := user_model.GetUserOrOrgIDByName(ctx, name)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.Flash.Error(ctx.Tr("form.user_not_exist"))
|
||||||
|
ctx.JSONErrorNotFound()
|
||||||
|
} else {
|
||||||
|
ctx.ServerError("GetUserOrOrgIDByName", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
actionsUnit, err := ctx.Repo.Repository.GetUnit(ctx, unit_model.TypeActions)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetUnit", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
actionsCfg := actionsUnit.ActionsConfig()
|
||||||
|
actionsCfg.AddCollaborativeOwner(ownerID)
|
||||||
|
if err := repo_model.UpdateRepoUnit(ctx, actionsUnit); err != nil {
|
||||||
|
ctx.ServerError("UpdateRepoUnit", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSONOK()
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteCollaborativeOwner(ctx *context.Context) {
|
||||||
|
ownerID := ctx.FormInt64("id")
|
||||||
|
|
||||||
|
actionsUnit, err := ctx.Repo.Repository.GetUnit(ctx, unit_model.TypeActions)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetUnit", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
actionsCfg := actionsUnit.ActionsConfig()
|
||||||
|
if !actionsCfg.IsCollaborativeOwner(ownerID) {
|
||||||
|
ctx.Flash.Error(ctx.Tr("actions.general.collaborative_owner_not_exist"))
|
||||||
|
ctx.JSONErrorNotFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
actionsCfg.RemoveCollaborativeOwner(ownerID)
|
||||||
|
if err := repo_model.UpdateRepoUnit(ctx, actionsUnit); err != nil {
|
||||||
|
ctx.ServerError("UpdateRepoUnit", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSONOK()
|
||||||
|
}
|
||||||
@@ -613,12 +613,6 @@ func handleSettingsPostAdvanced(ctx *context.Context) {
|
|||||||
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePackages)
|
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePackages)
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.EnableActions && !unit_model.TypeActions.UnitGlobalDisabled() {
|
|
||||||
units = append(units, newRepoUnit(repo, unit_model.TypeActions, nil))
|
|
||||||
} else if !unit_model.TypeActions.UnitGlobalDisabled() {
|
|
||||||
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeActions)
|
|
||||||
}
|
|
||||||
|
|
||||||
if form.EnablePulls && !unit_model.TypePullRequests.UnitGlobalDisabled() {
|
if form.EnablePulls && !unit_model.TypePullRequests.UnitGlobalDisabled() {
|
||||||
units = append(units, newRepoUnit(repo, unit_model.TypePullRequests, &repo_model.PullRequestsConfig{
|
units = append(units, newRepoUnit(repo, unit_model.TypePullRequests, &repo_model.PullRequestsConfig{
|
||||||
IgnoreWhitespaceConflicts: form.PullsIgnoreWhitespace,
|
IgnoreWhitespaceConflicts: form.PullsIgnoreWhitespace,
|
||||||
|
|||||||
@@ -16,10 +16,14 @@ import (
|
|||||||
|
|
||||||
// SearchCandidates searches candidate users for dropdown list
|
// SearchCandidates searches candidate users for dropdown list
|
||||||
func SearchCandidates(ctx *context.Context) {
|
func SearchCandidates(ctx *context.Context) {
|
||||||
|
searchUserTypes := []user_model.UserType{user_model.UserTypeIndividual}
|
||||||
|
if ctx.FormBool("orgs") {
|
||||||
|
searchUserTypes = append(searchUserTypes, user_model.UserTypeOrganization)
|
||||||
|
}
|
||||||
users, _, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
|
users, _, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
|
||||||
Actor: ctx.Doer,
|
Actor: ctx.Doer,
|
||||||
Keyword: ctx.FormTrim("q"),
|
Keyword: ctx.FormTrim("q"),
|
||||||
Type: user_model.UserTypeIndividual,
|
Types: searchUserTypes,
|
||||||
IsActive: optional.Some(true),
|
IsActive: optional.Some(true),
|
||||||
ListOptions: db.ListOptions{PageSize: setting.UI.MembersPagingNum},
|
ListOptions: db.ListOptions{PageSize: setting.UI.MembersPagingNum},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1159,11 +1159,21 @@ func registerWebRoutes(m *web.Router) {
|
|||||||
m.Post("/{lid}/unlock", repo_setting.LFSUnlock)
|
m.Post("/{lid}/unlock", repo_setting.LFSUnlock)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
m.Group("/actions/general", func() {
|
||||||
|
m.Get("", repo_setting.ActionsGeneralSettings)
|
||||||
|
m.Post("/actions_unit", repo_setting.ActionsUnitPost)
|
||||||
|
})
|
||||||
m.Group("/actions", func() {
|
m.Group("/actions", func() {
|
||||||
m.Get("", shared_actions.RedirectToDefaultSetting)
|
m.Get("", shared_actions.RedirectToDefaultSetting)
|
||||||
addSettingsRunnersRoutes()
|
addSettingsRunnersRoutes()
|
||||||
addSettingsSecretsRoutes()
|
addSettingsSecretsRoutes()
|
||||||
addSettingsVariablesRoutes()
|
addSettingsVariablesRoutes()
|
||||||
|
m.Group("/general", func() {
|
||||||
|
m.Group("/collaborative_owner", func() {
|
||||||
|
m.Post("/add", repo_setting.AddCollaborativeOwner)
|
||||||
|
m.Post("/delete", repo_setting.DeleteCollaborativeOwner)
|
||||||
|
})
|
||||||
|
})
|
||||||
}, actions.MustEnableActions)
|
}, actions.MustEnableActions)
|
||||||
// the follow handler must be under "settings", otherwise this incomplete repo can't be accessed
|
// the follow handler must be under "settings", otherwise this incomplete repo can't be accessed
|
||||||
m.Group("/migrate", func() {
|
m.Group("/migrate", func() {
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
{{template "shared/secrets/add_list" .}}
|
{{template "shared/secrets/add_list" .}}
|
||||||
{{else if eq .PageType "variables"}}
|
{{else if eq .PageType "variables"}}
|
||||||
{{template "shared/variables/variable_list" .}}
|
{{template "shared/variables/variable_list" .}}
|
||||||
|
{{else if eq .PageType "general"}}
|
||||||
|
{{template "repo/settings/actions_general" .}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{template "repo/settings/layout_footer" .}}
|
{{template "repo/settings/layout_footer" .}}
|
||||||
|
|||||||
69
templates/repo/settings/actions_general.tmpl
Normal file
69
templates/repo/settings/actions_general.tmpl
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<div class="repo-setting-content">
|
||||||
|
<h4 class="ui top attached header">
|
||||||
|
{{ctx.Locale.Tr "actions.general.enable_actions"}}
|
||||||
|
</h4>
|
||||||
|
<div class="ui attached segment">
|
||||||
|
<form class="ui form" action="{{.Link}}/actions_unit" method="post">
|
||||||
|
{{.CsrfTokenHtml}}
|
||||||
|
{{$isActionsEnabled := .Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypeActions}}
|
||||||
|
{{$isActionsGlobalDisabled := ctx.Consts.RepoUnitTypeActions.UnitGlobalDisabled}}
|
||||||
|
<div class="inline field">
|
||||||
|
<label>{{ctx.Locale.Tr "actions.actions"}}</label>
|
||||||
|
<div class="ui checkbox{{if $isActionsGlobalDisabled}} disabled{{end}}"{{if $isActionsGlobalDisabled}} data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{end}}>
|
||||||
|
<input class="enable-system" name="enable_actions" type="checkbox" {{if $isActionsGlobalDisabled}}disabled{{end}} {{if $isActionsEnabled}}checked{{end}}>
|
||||||
|
<label>{{ctx.Locale.Tr "repo.settings.actions_desc"}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{if not $isActionsGlobalDisabled}}
|
||||||
|
<div class="divider"></div>
|
||||||
|
<div class="field">
|
||||||
|
<button class="ui primary button">{{ctx.Locale.Tr "repo.settings.update_settings"}}</button>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{if and .EnableActions (.Permission.CanRead ctx.Consts.RepoUnitTypeActions)}}
|
||||||
|
{{if .Repository.IsPrivate}}
|
||||||
|
<h4 class="ui top attached header">
|
||||||
|
{{ctx.Locale.Tr "actions.general.collaborative_owners_management"}}
|
||||||
|
</h4>
|
||||||
|
{{if len .CollaborativeOwners}}
|
||||||
|
<div class="ui attached segment">
|
||||||
|
<div class="flex-list">
|
||||||
|
{{range .CollaborativeOwners}}
|
||||||
|
<div class="flex-item tw-items-center">
|
||||||
|
<div class="flex-item-leading">
|
||||||
|
<a href="{{.HomeLink}}">{{ctx.AvatarUtils.Avatar . 32}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="flex-item-main">
|
||||||
|
<div class="flex-item-title">
|
||||||
|
{{template "shared/user/name" .}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-item-trailing">
|
||||||
|
<button class="ui red tiny button inline link-action"
|
||||||
|
data-url="{{$.Link}}/collaborative_owner/delete?id={{.ID}}"
|
||||||
|
data-modal-confirm-header="{{ctx.Locale.Tr "actions.general.remove_collaborative_owner"}}"
|
||||||
|
data-modal-confirm-content="{{ctx.Locale.Tr "actions.general.remove_collaborative_owner_desc"}}"
|
||||||
|
>{{ctx.Locale.Tr "remove"}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
<div class="ui bottom attached segment">
|
||||||
|
<form class="ui form form-fetch-action" action="{{.Link}}/collaborative_owner/add" method="post">
|
||||||
|
{{.CsrfTokenHtml}}
|
||||||
|
<div id="search-user-box" class="ui search input tw-align-middle" data-include-orgs="true">
|
||||||
|
<input class="prompt" name="collaborative_owner" placeholder="{{ctx.Locale.Tr "search.user_kind"}}" autocomplete="off" autofocus required>
|
||||||
|
</div>
|
||||||
|
<button class="ui primary button">{{ctx.Locale.Tr "actions.general.add_collaborative_owner"}}</button>
|
||||||
|
</form>
|
||||||
|
<br>
|
||||||
|
{{ctx.Locale.Tr "actions.general.collaborative_owners_management_help"}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
@@ -38,10 +38,13 @@
|
|||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if and .EnableActions (.Permission.CanRead ctx.Consts.RepoUnitTypeActions)}}
|
<details class="item toggleable-item" {{if or .PageIsSharedSettingsRunners .PageIsSharedSettingsSecrets .PageIsSharedSettingsVariables .PageIsActionsSettingsGeneral}}open{{end}}>
|
||||||
<details class="item toggleable-item" {{if or .PageIsSharedSettingsRunners .PageIsSharedSettingsSecrets .PageIsSharedSettingsVariables}}open{{end}}>
|
|
||||||
<summary>{{ctx.Locale.Tr "actions.actions"}}</summary>
|
<summary>{{ctx.Locale.Tr "actions.actions"}}</summary>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
|
<a class="{{if .PageIsActionsSettingsGeneral}}active {{end}}item" href="{{.RepoLink}}/settings/actions/general">
|
||||||
|
{{ctx.Locale.Tr "actions.general"}}
|
||||||
|
</a>
|
||||||
|
{{if and .EnableActions (.Permission.CanRead ctx.Consts.RepoUnitTypeActions)}}
|
||||||
<a class="{{if .PageIsSharedSettingsRunners}}active {{end}}item" href="{{.RepoLink}}/settings/actions/runners">
|
<a class="{{if .PageIsSharedSettingsRunners}}active {{end}}item" href="{{.RepoLink}}/settings/actions/runners">
|
||||||
{{ctx.Locale.Tr "actions.runners"}}
|
{{ctx.Locale.Tr "actions.runners"}}
|
||||||
</a>
|
</a>
|
||||||
@@ -51,8 +54,8 @@
|
|||||||
<a class="{{if .PageIsSharedSettingsVariables}}active {{end}}item" href="{{.RepoLink}}/settings/actions/variables">
|
<a class="{{if .PageIsSharedSettingsVariables}}active {{end}}item" href="{{.RepoLink}}/settings/actions/variables">
|
||||||
{{ctx.Locale.Tr "actions.variables"}}
|
{{ctx.Locale.Tr "actions.variables"}}
|
||||||
</a>
|
</a>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -509,18 +509,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{if .EnableActions}}
|
|
||||||
{{$isActionsEnabled := .Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypeActions}}
|
|
||||||
{{$isActionsGlobalDisabled := ctx.Consts.RepoUnitTypeActions.UnitGlobalDisabled}}
|
|
||||||
<div class="inline field">
|
|
||||||
<label>{{ctx.Locale.Tr "actions.actions"}}</label>
|
|
||||||
<div class="ui checkbox{{if $isActionsGlobalDisabled}} disabled{{end}}"{{if $isActionsGlobalDisabled}} data-tooltip-content="{{ctx.Locale.Tr "repo.unit_disabled"}}"{{end}}>
|
|
||||||
<input class="enable-system" name="enable_actions" type="checkbox" {{if $isActionsEnabled}}checked{{end}}>
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.actions_desc"}}</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{if not .IsMirror}}
|
{{if not .IsMirror}}
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
{{$pullRequestEnabled := .Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypePullRequests}}
|
{{$pullRequestEnabled := .Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypePullRequests}}
|
||||||
|
|||||||
62
tests/integration/actions_settings_test.go
Normal file
62
tests/integration/actions_settings_test.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestActionsCollaborativeOwner(t *testing.T) {
|
||||||
|
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||||
|
// user2 is the owner of "reusable_workflow" repo
|
||||||
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
user2Session := loginUser(t, user2.Name)
|
||||||
|
user2Token := getTokenForLoggedInUser(t, user2Session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
|
||||||
|
repo := createActionsTestRepo(t, user2Token, "reusable_workflow", true)
|
||||||
|
|
||||||
|
// a private repo(id=6) of user10 will try to clone "reusable_workflow" repo
|
||||||
|
user10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10})
|
||||||
|
// task id is 55 and its repo_id=6
|
||||||
|
task := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 55, RepoID: 6})
|
||||||
|
taskToken := "674f727a81ed2f195bccab036cccf86a182199eb"
|
||||||
|
tokenHash := auth_model.HashToken(taskToken, task.TokenSalt)
|
||||||
|
assert.Equal(t, task.TokenHash, tokenHash)
|
||||||
|
|
||||||
|
dstPath := t.TempDir()
|
||||||
|
u.Path = fmt.Sprintf("%s/%s.git", repo.Owner.UserName, repo.Name)
|
||||||
|
u.User = url.UserPassword("gitea-actions", taskToken)
|
||||||
|
|
||||||
|
// the git clone will fail
|
||||||
|
doGitCloneFail(u)(t)
|
||||||
|
|
||||||
|
// add user10 to the list of collaborative owners
|
||||||
|
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/collaborative_owner/add", repo.Owner.UserName, repo.Name), map[string]string{
|
||||||
|
"_csrf": GetUserCSRFToken(t, user2Session),
|
||||||
|
"collaborative_owner": user10.Name,
|
||||||
|
})
|
||||||
|
user2Session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
// the git clone will be successful
|
||||||
|
doGitClone(dstPath, u)(t)
|
||||||
|
|
||||||
|
// remove user10 from the list of collaborative owners
|
||||||
|
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/collaborative_owner/delete?id=%d", repo.Owner.UserName, repo.Name, user10.ID), map[string]string{
|
||||||
|
"_csrf": GetUserCSRFToken(t, user2Session),
|
||||||
|
})
|
||||||
|
user2Session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
// the git clone will fail
|
||||||
|
doGitCloneFail(u)(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -10,10 +10,11 @@ export function initCompSearchUserBox() {
|
|||||||
|
|
||||||
const allowEmailInput = searchUserBox.getAttribute('data-allow-email') === 'true';
|
const allowEmailInput = searchUserBox.getAttribute('data-allow-email') === 'true';
|
||||||
const allowEmailDescription = searchUserBox.getAttribute('data-allow-email-description') ?? undefined;
|
const allowEmailDescription = searchUserBox.getAttribute('data-allow-email-description') ?? undefined;
|
||||||
|
const includeOrgs = searchUserBox.getAttribute('data-include-orgs') === 'true';
|
||||||
fomanticQuery(searchUserBox).search({
|
fomanticQuery(searchUserBox).search({
|
||||||
minCharacters: 2,
|
minCharacters: 2,
|
||||||
apiSettings: {
|
apiSettings: {
|
||||||
url: `${appSubUrl}/user/search_candidates?q={query}`,
|
url: `${appSubUrl}/user/search_candidates?q={query}&orgs=${includeOrgs}`,
|
||||||
onResponse(response: any) {
|
onResponse(response: any) {
|
||||||
const resultItems = [];
|
const resultItems = [];
|
||||||
const searchQuery = searchUserBox.querySelector('input').value;
|
const searchQuery = searchUserBox.querySelector('input').value;
|
||||||
|
|||||||
Reference in New Issue
Block a user