| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597 | 
							- // Copyright 2015 The Gogs Authors. All rights reserved.
 
- // Use of this source code is governed by a MIT-style
 
- // license that can be found in the LICENSE file.
 
- package repo
 
- import (
 
- 	"fmt"
 
- 	"net/http"
 
- 	"net/url"
 
- 	"strings"
 
- 	"time"
 
- 	"github.com/gogs/git-module"
 
- 	api "github.com/gogs/go-gogs-client"
 
- 	jsoniter "github.com/json-iterator/go"
 
- 	"gopkg.in/macaron.v1"
 
- 	"gogs.io/gogs/internal/conf"
 
- 	"gogs.io/gogs/internal/context"
 
- 	"gogs.io/gogs/internal/db"
 
- 	"gogs.io/gogs/internal/db/errors"
 
- 	"gogs.io/gogs/internal/form"
 
- 	"gogs.io/gogs/internal/netutil"
 
- )
 
- const (
 
- 	tmplRepoSettingsWebhooks   = "repo/settings/webhook/base"
 
- 	tmplRepoSettingsWebhookNew = "repo/settings/webhook/new"
 
- 	tmplOrgSettingsWebhooks    = "org/settings/webhooks"
 
- 	tmplOrgSettingsWebhookNew  = "org/settings/webhook_new"
 
- )
 
- func InjectOrgRepoContext() macaron.Handler {
 
- 	return func(c *context.Context) {
 
- 		orCtx, err := getOrgRepoContext(c)
 
- 		if err != nil {
 
- 			c.Error(err, "get organization or repository context")
 
- 			return
 
- 		}
 
- 		c.Map(orCtx)
 
- 	}
 
- }
 
- type orgRepoContext struct {
 
- 	OrgID    int64
 
- 	RepoID   int64
 
- 	Link     string
 
- 	TmplList string
 
- 	TmplNew  string
 
- }
 
- // getOrgRepoContext determines whether this is a repo context or organization context.
 
- func getOrgRepoContext(c *context.Context) (*orgRepoContext, error) {
 
- 	if len(c.Repo.RepoLink) > 0 {
 
- 		c.PageIs("RepositoryContext")
 
- 		return &orgRepoContext{
 
- 			RepoID:   c.Repo.Repository.ID,
 
- 			Link:     c.Repo.RepoLink,
 
- 			TmplList: tmplRepoSettingsWebhooks,
 
- 			TmplNew:  tmplRepoSettingsWebhookNew,
 
- 		}, nil
 
- 	}
 
- 	if len(c.Org.OrgLink) > 0 {
 
- 		c.PageIs("OrganizationContext")
 
- 		return &orgRepoContext{
 
- 			OrgID:    c.Org.Organization.ID,
 
- 			Link:     c.Org.OrgLink,
 
- 			TmplList: tmplOrgSettingsWebhooks,
 
- 			TmplNew:  tmplOrgSettingsWebhookNew,
 
- 		}, nil
 
- 	}
 
- 	return nil, errors.New("unable to determine context")
 
- }
 
- func Webhooks(c *context.Context, orCtx *orgRepoContext) {
 
- 	c.Title("repo.settings.hooks")
 
- 	c.PageIs("SettingsHooks")
 
- 	c.Data["Types"] = conf.Webhook.Types
 
- 	var err error
 
- 	var ws []*db.Webhook
 
- 	if orCtx.RepoID > 0 {
 
- 		c.Data["Description"] = c.Tr("repo.settings.hooks_desc", "https://gogs.io/docs/features/webhook.html")
 
- 		ws, err = db.GetWebhooksByRepoID(orCtx.RepoID)
 
- 	} else {
 
- 		c.Data["Description"] = c.Tr("org.settings.hooks_desc")
 
- 		ws, err = db.GetWebhooksByOrgID(orCtx.OrgID)
 
- 	}
 
- 	if err != nil {
 
- 		c.Error(err, "get webhooks")
 
- 		return
 
- 	}
 
- 	c.Data["Webhooks"] = ws
 
- 	c.Success(orCtx.TmplList)
 
- }
 
- func WebhooksNew(c *context.Context, orCtx *orgRepoContext) {
 
- 	c.Title("repo.settings.add_webhook")
 
- 	c.PageIs("SettingsHooks")
 
- 	c.PageIs("SettingsHooksNew")
 
- 	allowed := false
 
- 	hookType := strings.ToLower(c.Params(":type"))
 
- 	for _, typ := range conf.Webhook.Types {
 
- 		if hookType == typ {
 
- 			allowed = true
 
- 			c.Data["HookType"] = typ
 
- 			break
 
- 		}
 
- 	}
 
- 	if !allowed {
 
- 		c.NotFound()
 
- 		return
 
- 	}
 
- 	c.Success(orCtx.TmplNew)
 
- }
 
- func validateWebhook(l macaron.Locale, w *db.Webhook) (field, msg string, ok bool) {
 
- 	// 🚨 SECURITY: Local addresses must not be allowed by non-admins to prevent SSRF,
 
- 	// see https://github.com/gogs/gogs/issues/5366 for details.
 
- 	payloadURL, err := url.Parse(w.URL)
 
- 	if err != nil {
 
- 		return "PayloadURL", l.Tr("repo.settings.webhook.err_cannot_parse_payload_url", err), false
 
- 	}
 
- 	if netutil.IsBlockedLocalHostname(payloadURL.Hostname(), conf.Security.LocalNetworkAllowlist) {
 
- 		return "PayloadURL", l.Tr("repo.settings.webhook.url_resolved_to_blocked_local_address"), false
 
- 	}
 
- 	return "", "", true
 
- }
 
- func validateAndCreateWebhook(c *context.Context, orCtx *orgRepoContext, w *db.Webhook) {
 
- 	c.Data["Webhook"] = w
 
- 	if c.HasError() {
 
- 		c.Success(orCtx.TmplNew)
 
- 		return
 
- 	}
 
- 	field, msg, ok := validateWebhook(c.Locale, w)
 
- 	if !ok {
 
- 		c.FormErr(field)
 
- 		c.RenderWithErr(msg, orCtx.TmplNew, nil)
 
- 		return
 
- 	}
 
- 	if err := w.UpdateEvent(); err != nil {
 
- 		c.Error(err, "update event")
 
- 		return
 
- 	} else if err := db.CreateWebhook(w); err != nil {
 
- 		c.Error(err, "create webhook")
 
- 		return
 
- 	}
 
- 	c.Flash.Success(c.Tr("repo.settings.add_hook_success"))
 
- 	c.Redirect(orCtx.Link + "/settings/hooks")
 
- }
 
- func toHookEvent(f form.Webhook) *db.HookEvent {
 
- 	return &db.HookEvent{
 
- 		PushOnly:       f.PushOnly(),
 
- 		SendEverything: f.SendEverything(),
 
- 		ChooseEvents:   f.ChooseEvents(),
 
- 		HookEvents: db.HookEvents{
 
- 			Create:       f.Create,
 
- 			Delete:       f.Delete,
 
- 			Fork:         f.Fork,
 
- 			Push:         f.Push,
 
- 			Issues:       f.Issues,
 
- 			IssueComment: f.IssueComment,
 
- 			PullRequest:  f.PullRequest,
 
- 			Release:      f.Release,
 
- 		},
 
- 	}
 
- }
 
- func WebhooksNewPost(c *context.Context, orCtx *orgRepoContext, f form.NewWebhook) {
 
- 	c.Title("repo.settings.add_webhook")
 
- 	c.PageIs("SettingsHooks")
 
- 	c.PageIs("SettingsHooksNew")
 
- 	c.Data["HookType"] = "gogs"
 
- 	contentType := db.JSON
 
- 	if db.HookContentType(f.ContentType) == db.FORM {
 
- 		contentType = db.FORM
 
- 	}
 
- 	w := &db.Webhook{
 
- 		RepoID:       orCtx.RepoID,
 
- 		OrgID:        orCtx.OrgID,
 
- 		URL:          f.PayloadURL,
 
- 		ContentType:  contentType,
 
- 		Secret:       f.Secret,
 
- 		HookEvent:    toHookEvent(f.Webhook),
 
- 		IsActive:     f.Active,
 
- 		HookTaskType: db.GOGS,
 
- 	}
 
- 	validateAndCreateWebhook(c, orCtx, w)
 
- }
 
- func WebhooksSlackNewPost(c *context.Context, orCtx *orgRepoContext, f form.NewSlackHook) {
 
- 	c.Title("repo.settings.add_webhook")
 
- 	c.PageIs("SettingsHooks")
 
- 	c.PageIs("SettingsHooksNew")
 
- 	c.Data["HookType"] = "slack"
 
- 	meta := &db.SlackMeta{
 
- 		Channel:  f.Channel,
 
- 		Username: f.Username,
 
- 		IconURL:  f.IconURL,
 
- 		Color:    f.Color,
 
- 	}
 
- 	c.Data["SlackMeta"] = meta
 
- 	p, err := jsoniter.Marshal(meta)
 
- 	if err != nil {
 
- 		c.Error(err, "marshal JSON")
 
- 		return
 
- 	}
 
- 	w := &db.Webhook{
 
- 		RepoID:       orCtx.RepoID,
 
- 		URL:          f.PayloadURL,
 
- 		ContentType:  db.JSON,
 
- 		HookEvent:    toHookEvent(f.Webhook),
 
- 		IsActive:     f.Active,
 
- 		HookTaskType: db.SLACK,
 
- 		Meta:         string(p),
 
- 		OrgID:        orCtx.OrgID,
 
- 	}
 
- 	validateAndCreateWebhook(c, orCtx, w)
 
- }
 
- func WebhooksDiscordNewPost(c *context.Context, orCtx *orgRepoContext, f form.NewDiscordHook) {
 
- 	c.Title("repo.settings.add_webhook")
 
- 	c.PageIs("SettingsHooks")
 
- 	c.PageIs("SettingsHooksNew")
 
- 	c.Data["HookType"] = "discord"
 
- 	meta := &db.SlackMeta{
 
- 		Username: f.Username,
 
- 		IconURL:  f.IconURL,
 
- 		Color:    f.Color,
 
- 	}
 
- 	c.Data["SlackMeta"] = meta
 
- 	p, err := jsoniter.Marshal(meta)
 
- 	if err != nil {
 
- 		c.Error(err, "marshal JSON")
 
- 		return
 
- 	}
 
- 	w := &db.Webhook{
 
- 		RepoID:       orCtx.RepoID,
 
- 		URL:          f.PayloadURL,
 
- 		ContentType:  db.JSON,
 
- 		HookEvent:    toHookEvent(f.Webhook),
 
- 		IsActive:     f.Active,
 
- 		HookTaskType: db.DISCORD,
 
- 		Meta:         string(p),
 
- 		OrgID:        orCtx.OrgID,
 
- 	}
 
- 	validateAndCreateWebhook(c, orCtx, w)
 
- }
 
- func WebhooksDingtalkNewPost(c *context.Context, orCtx *orgRepoContext, f form.NewDingtalkHook) {
 
- 	c.Title("repo.settings.add_webhook")
 
- 	c.PageIs("SettingsHooks")
 
- 	c.PageIs("SettingsHooksNew")
 
- 	c.Data["HookType"] = "dingtalk"
 
- 	w := &db.Webhook{
 
- 		RepoID:       orCtx.RepoID,
 
- 		URL:          f.PayloadURL,
 
- 		ContentType:  db.JSON,
 
- 		HookEvent:    toHookEvent(f.Webhook),
 
- 		IsActive:     f.Active,
 
- 		HookTaskType: db.DINGTALK,
 
- 		OrgID:        orCtx.OrgID,
 
- 	}
 
- 	validateAndCreateWebhook(c, orCtx, w)
 
- }
 
- func loadWebhook(c *context.Context, orCtx *orgRepoContext) *db.Webhook {
 
- 	c.RequireHighlightJS()
 
- 	var err error
 
- 	var w *db.Webhook
 
- 	if orCtx.RepoID > 0 {
 
- 		w, err = db.GetWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
 
- 	} else {
 
- 		w, err = db.GetWebhookByOrgID(c.Org.Organization.ID, c.ParamsInt64(":id"))
 
- 	}
 
- 	if err != nil {
 
- 		c.NotFoundOrError(err, "get webhook")
 
- 		return nil
 
- 	}
 
- 	c.Data["Webhook"] = w
 
- 	switch w.HookTaskType {
 
- 	case db.SLACK:
 
- 		c.Data["SlackMeta"] = w.SlackMeta()
 
- 		c.Data["HookType"] = "slack"
 
- 	case db.DISCORD:
 
- 		c.Data["SlackMeta"] = w.SlackMeta()
 
- 		c.Data["HookType"] = "discord"
 
- 	case db.DINGTALK:
 
- 		c.Data["HookType"] = "dingtalk"
 
- 	default:
 
- 		c.Data["HookType"] = "gogs"
 
- 	}
 
- 	c.Data["FormURL"] = fmt.Sprintf("%s/settings/hooks/%s/%d", orCtx.Link, c.Data["HookType"], w.ID)
 
- 	c.Data["DeleteURL"] = fmt.Sprintf("%s/settings/hooks/delete", orCtx.Link)
 
- 	c.Data["History"], err = w.History(1)
 
- 	if err != nil {
 
- 		c.Error(err, "get history")
 
- 		return nil
 
- 	}
 
- 	return w
 
- }
 
- func WebhooksEdit(c *context.Context, orCtx *orgRepoContext) {
 
- 	c.Title("repo.settings.update_webhook")
 
- 	c.PageIs("SettingsHooks")
 
- 	c.PageIs("SettingsHooksEdit")
 
- 	loadWebhook(c, orCtx)
 
- 	if c.Written() {
 
- 		return
 
- 	}
 
- 	c.Success(orCtx.TmplNew)
 
- }
 
- func validateAndUpdateWebhook(c *context.Context, orCtx *orgRepoContext, w *db.Webhook) {
 
- 	c.Data["Webhook"] = w
 
- 	if c.HasError() {
 
- 		c.Success(orCtx.TmplNew)
 
- 		return
 
- 	}
 
- 	field, msg, ok := validateWebhook(c.Locale, w)
 
- 	if !ok {
 
- 		c.FormErr(field)
 
- 		c.RenderWithErr(msg, orCtx.TmplNew, nil)
 
- 		return
 
- 	}
 
- 	if err := w.UpdateEvent(); err != nil {
 
- 		c.Error(err, "update event")
 
- 		return
 
- 	} else if err := db.UpdateWebhook(w); err != nil {
 
- 		c.Error(err, "update webhook")
 
- 		return
 
- 	}
 
- 	c.Flash.Success(c.Tr("repo.settings.update_hook_success"))
 
- 	c.Redirect(fmt.Sprintf("%s/settings/hooks/%d", orCtx.Link, w.ID))
 
- }
 
- func WebhooksEditPost(c *context.Context, orCtx *orgRepoContext, f form.NewWebhook) {
 
- 	c.Title("repo.settings.update_webhook")
 
- 	c.PageIs("SettingsHooks")
 
- 	c.PageIs("SettingsHooksEdit")
 
- 	w := loadWebhook(c, orCtx)
 
- 	if c.Written() {
 
- 		return
 
- 	}
 
- 	contentType := db.JSON
 
- 	if db.HookContentType(f.ContentType) == db.FORM {
 
- 		contentType = db.FORM
 
- 	}
 
- 	w.URL = f.PayloadURL
 
- 	w.ContentType = contentType
 
- 	w.Secret = f.Secret
 
- 	w.HookEvent = toHookEvent(f.Webhook)
 
- 	w.IsActive = f.Active
 
- 	validateAndUpdateWebhook(c, orCtx, w)
 
- }
 
- func WebhooksSlackEditPost(c *context.Context, orCtx *orgRepoContext, f form.NewSlackHook) {
 
- 	c.Title("repo.settings.update_webhook")
 
- 	c.PageIs("SettingsHooks")
 
- 	c.PageIs("SettingsHooksEdit")
 
- 	w := loadWebhook(c, orCtx)
 
- 	if c.Written() {
 
- 		return
 
- 	}
 
- 	meta, err := jsoniter.Marshal(&db.SlackMeta{
 
- 		Channel:  f.Channel,
 
- 		Username: f.Username,
 
- 		IconURL:  f.IconURL,
 
- 		Color:    f.Color,
 
- 	})
 
- 	if err != nil {
 
- 		c.Error(err, "marshal JSON")
 
- 		return
 
- 	}
 
- 	w.URL = f.PayloadURL
 
- 	w.Meta = string(meta)
 
- 	w.HookEvent = toHookEvent(f.Webhook)
 
- 	w.IsActive = f.Active
 
- 	validateAndUpdateWebhook(c, orCtx, w)
 
- }
 
- func WebhooksDiscordEditPost(c *context.Context, orCtx *orgRepoContext, f form.NewDiscordHook) {
 
- 	c.Title("repo.settings.update_webhook")
 
- 	c.PageIs("SettingsHooks")
 
- 	c.PageIs("SettingsHooksEdit")
 
- 	w := loadWebhook(c, orCtx)
 
- 	if c.Written() {
 
- 		return
 
- 	}
 
- 	meta, err := jsoniter.Marshal(&db.SlackMeta{
 
- 		Username: f.Username,
 
- 		IconURL:  f.IconURL,
 
- 		Color:    f.Color,
 
- 	})
 
- 	if err != nil {
 
- 		c.Error(err, "marshal JSON")
 
- 		return
 
- 	}
 
- 	w.URL = f.PayloadURL
 
- 	w.Meta = string(meta)
 
- 	w.HookEvent = toHookEvent(f.Webhook)
 
- 	w.IsActive = f.Active
 
- 	validateAndUpdateWebhook(c, orCtx, w)
 
- }
 
- func WebhooksDingtalkEditPost(c *context.Context, orCtx *orgRepoContext, f form.NewDingtalkHook) {
 
- 	c.Title("repo.settings.update_webhook")
 
- 	c.PageIs("SettingsHooks")
 
- 	c.PageIs("SettingsHooksEdit")
 
- 	w := loadWebhook(c, orCtx)
 
- 	if c.Written() {
 
- 		return
 
- 	}
 
- 	w.URL = f.PayloadURL
 
- 	w.HookEvent = toHookEvent(f.Webhook)
 
- 	w.IsActive = f.Active
 
- 	validateAndUpdateWebhook(c, orCtx, w)
 
- }
 
- func TestWebhook(c *context.Context) {
 
- 	var (
 
- 		commitID          string
 
- 		commitMessage     string
 
- 		author            *git.Signature
 
- 		committer         *git.Signature
 
- 		authorUsername    string
 
- 		committerUsername string
 
- 		nameStatus        *git.NameStatus
 
- 	)
 
- 	// Grab latest commit or fake one if it's empty repository.
 
- 	if c.Repo.Commit == nil {
 
- 		commitID = git.EmptyID
 
- 		commitMessage = "This is a fake commit"
 
- 		ghost := db.NewGhostUser()
 
- 		author = &git.Signature{
 
- 			Name:  ghost.DisplayName(),
 
- 			Email: ghost.Email,
 
- 			When:  time.Now(),
 
- 		}
 
- 		committer = author
 
- 		authorUsername = ghost.Name
 
- 		committerUsername = ghost.Name
 
- 		nameStatus = &git.NameStatus{}
 
- 	} else {
 
- 		commitID = c.Repo.Commit.ID.String()
 
- 		commitMessage = c.Repo.Commit.Message
 
- 		author = c.Repo.Commit.Author
 
- 		committer = c.Repo.Commit.Committer
 
- 		// Try to match email with a real user.
 
- 		author, err := db.Users.GetByEmail(c.Req.Context(), c.Repo.Commit.Author.Email)
 
- 		if err == nil {
 
- 			authorUsername = author.Name
 
- 		} else if !db.IsErrUserNotExist(err) {
 
- 			c.Error(err, "get user by email")
 
- 			return
 
- 		}
 
- 		user, err := db.Users.GetByEmail(c.Req.Context(), c.Repo.Commit.Committer.Email)
 
- 		if err == nil {
 
- 			committerUsername = user.Name
 
- 		} else if !db.IsErrUserNotExist(err) {
 
- 			c.Error(err, "get user by email")
 
- 			return
 
- 		}
 
- 		nameStatus, err = c.Repo.Commit.ShowNameStatus()
 
- 		if err != nil {
 
- 			c.Error(err, "get changed files")
 
- 			return
 
- 		}
 
- 	}
 
- 	apiUser := c.User.APIFormat()
 
- 	p := &api.PushPayload{
 
- 		Ref:    git.RefsHeads + c.Repo.Repository.DefaultBranch,
 
- 		Before: commitID,
 
- 		After:  commitID,
 
- 		Commits: []*api.PayloadCommit{
 
- 			{
 
- 				ID:      commitID,
 
- 				Message: commitMessage,
 
- 				URL:     c.Repo.Repository.HTMLURL() + "/commit/" + commitID,
 
- 				Author: &api.PayloadUser{
 
- 					Name:     author.Name,
 
- 					Email:    author.Email,
 
- 					UserName: authorUsername,
 
- 				},
 
- 				Committer: &api.PayloadUser{
 
- 					Name:     committer.Name,
 
- 					Email:    committer.Email,
 
- 					UserName: committerUsername,
 
- 				},
 
- 				Added:    nameStatus.Added,
 
- 				Removed:  nameStatus.Removed,
 
- 				Modified: nameStatus.Modified,
 
- 			},
 
- 		},
 
- 		Repo:   c.Repo.Repository.APIFormatLegacy(nil),
 
- 		Pusher: apiUser,
 
- 		Sender: apiUser,
 
- 	}
 
- 	if err := db.TestWebhook(c.Repo.Repository, db.HOOK_EVENT_PUSH, p, c.ParamsInt64("id")); err != nil {
 
- 		c.Error(err, "test webhook")
 
- 		return
 
- 	}
 
- 	c.Flash.Info(c.Tr("repo.settings.webhook.test_delivery_success"))
 
- 	c.Status(http.StatusOK)
 
- }
 
- func RedeliveryWebhook(c *context.Context) {
 
- 	webhook, err := db.GetWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
 
- 	if err != nil {
 
- 		c.NotFoundOrError(err, "get webhook")
 
- 		return
 
- 	}
 
- 	hookTask, err := db.GetHookTaskOfWebhookByUUID(webhook.ID, c.Query("uuid"))
 
- 	if err != nil {
 
- 		c.NotFoundOrError(err, "get hook task by UUID")
 
- 		return
 
- 	}
 
- 	hookTask.IsDelivered = false
 
- 	if err = db.UpdateHookTask(hookTask); err != nil {
 
- 		c.Error(err, "update hook task")
 
- 		return
 
- 	}
 
- 	go db.HookQueue.Add(c.Repo.Repository.ID)
 
- 	c.Flash.Info(c.Tr("repo.settings.webhook.redelivery_success", hookTask.UUID))
 
- 	c.Status(http.StatusOK)
 
- }
 
- func DeleteWebhook(c *context.Context, orCtx *orgRepoContext) {
 
- 	var err error
 
- 	if orCtx.RepoID > 0 {
 
- 		err = db.DeleteWebhookOfRepoByID(orCtx.RepoID, c.QueryInt64("id"))
 
- 	} else {
 
- 		err = db.DeleteWebhookOfOrgByID(orCtx.OrgID, c.QueryInt64("id"))
 
- 	}
 
- 	if err != nil {
 
- 		c.Error(err, "delete webhook")
 
- 		return
 
- 	}
 
- 	c.Flash.Success(c.Tr("repo.settings.webhook_deletion_success"))
 
- 	c.JSONSuccess(map[string]any{
 
- 		"redirect": orCtx.Link + "/settings/hooks",
 
- 	})
 
- }
 
 
  |