diff --git a/docs/content/doc/features/webhooks.en-us.md b/docs/content/doc/features/webhooks.en-us.md
index 1a0a180e7a..ec50c013f5 100644
--- a/docs/content/doc/features/webhooks.en-us.md
+++ b/docs/content/doc/features/webhooks.en-us.md
@@ -15,24 +15,24 @@ menu:
# Webhooks
-Gitea supports web hooks for repository events. This can be found in the settings
-page `/:username/:reponame/settings/hooks`. All event pushes are POST requests.
-The methods currently supported are:
+Gitea supports web hooks for repository events. This can be configured in the settings
+page `/:username/:reponame/settings/hooks` by a repository admin. Webhooks can also be configured on a per-organization and whole system basis.
+All event pushes are POST requests. The methods currently supported are:
-- Gitea
+- Gitea (can also be a GET request)
- Gogs
- Slack
- Discord
- Dingtalk
- Telegram
- Microsoft Teams
+- Feishu
### Event information
The following is an example of event information that will be sent by Gitea to
a Payload URL:
-
```
X-GitHub-Delivery: f6266f16-1bf3-46a5-9ea4-602e06ead473
X-GitHub-Event: push
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index ba411dd8c2..2badb72788 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -194,6 +194,8 @@ var migrations = []Migration{
NewMigration("remove dependencies from deleted repositories", purgeUnusedDependencies),
// v130 -> v131
NewMigration("Expand webhooks for more granularity", expandWebhooks),
+ // v131 -> v132
+ NewMigration("Add IsSystemWebhook column to webhooks table", addSystemWebhookColumn),
}
// Migrate database to current version
diff --git a/models/migrations/v131.go b/models/migrations/v131.go
new file mode 100644
index 0000000000..a38c7be634
--- /dev/null
+++ b/models/migrations/v131.go
@@ -0,0 +1,22 @@
+// Copyright 2020 The Gitea 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 migrations
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func addSystemWebhookColumn(x *xorm.Engine) error {
+ type Webhook struct {
+ IsSystemWebhook bool `xorm:"NOT NULL DEFAULT false"`
+ }
+
+ if err := x.Sync2(new(Webhook)); err != nil {
+ return fmt.Errorf("Sync2: %v", err)
+ }
+ return nil
+}
diff --git a/models/webhook.go b/models/webhook.go
index 82aedf7e81..d161ca1ae9 100644
--- a/models/webhook.go
+++ b/models/webhook.go
@@ -99,21 +99,22 @@ const (
// Webhook represents a web hook object.
type Webhook struct {
- ID int64 `xorm:"pk autoincr"`
- RepoID int64 `xorm:"INDEX"`
- OrgID int64 `xorm:"INDEX"`
- URL string `xorm:"url TEXT"`
- Signature string `xorm:"TEXT"`
- HTTPMethod string `xorm:"http_method"`
- ContentType HookContentType
- Secret string `xorm:"TEXT"`
- Events string `xorm:"TEXT"`
- *HookEvent `xorm:"-"`
- IsSSL bool `xorm:"is_ssl"`
- IsActive bool `xorm:"INDEX"`
- HookTaskType HookTaskType
- Meta string `xorm:"TEXT"` // store hook-specific attributes
- LastStatus HookStatus // Last delivery status
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64 `xorm:"INDEX"` // An ID of 0 indicates either a default or system webhook
+ OrgID int64 `xorm:"INDEX"`
+ IsSystemWebhook bool
+ URL string `xorm:"url TEXT"`
+ Signature string `xorm:"TEXT"`
+ HTTPMethod string `xorm:"http_method"`
+ ContentType HookContentType
+ Secret string `xorm:"TEXT"`
+ Events string `xorm:"TEXT"`
+ *HookEvent `xorm:"-"`
+ IsSSL bool `xorm:"is_ssl"`
+ IsActive bool `xorm:"INDEX"`
+ HookTaskType HookTaskType
+ Meta string `xorm:"TEXT"` // store hook-specific attributes
+ LastStatus HookStatus // Last delivery status
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
@@ -401,7 +402,7 @@ func GetWebhooksByOrgID(orgID int64, listOptions ListOptions) ([]*Webhook, error
func GetDefaultWebhook(id int64) (*Webhook, error) {
webhook := &Webhook{ID: id}
has, err := x.
- Where("repo_id=? AND org_id=?", 0, 0).
+ Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, false).
Get(webhook)
if err != nil {
return nil, err
@@ -419,7 +420,33 @@ func GetDefaultWebhooks() ([]*Webhook, error) {
func getDefaultWebhooks(e Engine) ([]*Webhook, error) {
webhooks := make([]*Webhook, 0, 5)
return webhooks, e.
- Where("repo_id=? AND org_id=?", 0, 0).
+ Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, false).
+ Find(&webhooks)
+}
+
+// GetSystemWebhook returns admin system webhook by given ID.
+func GetSystemWebhook(id int64) (*Webhook, error) {
+ webhook := &Webhook{ID: id}
+ has, err := x.
+ Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, true).
+ Get(webhook)
+ if err != nil {
+ return nil, err
+ } else if !has {
+ return nil, ErrWebhookNotExist{id}
+ }
+ return webhook, nil
+}
+
+// GetSystemWebhooks returns all admin system webhooks.
+func GetSystemWebhooks() ([]*Webhook, error) {
+ return getSystemWebhooks(x)
+}
+
+func getSystemWebhooks(e Engine) ([]*Webhook, error) {
+ webhooks := make([]*Webhook, 0, 5)
+ return webhooks, e.
+ Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, true).
Find(&webhooks)
}
@@ -471,8 +498,8 @@ func DeleteWebhookByOrgID(orgID, id int64) error {
})
}
-// DeleteDefaultWebhook deletes an admin-default webhook by given ID.
-func DeleteDefaultWebhook(id int64) error {
+// DeleteDefaultSystemWebhook deletes an admin-configured default or system webhook (where Org and Repo ID both 0)
+func DeleteDefaultSystemWebhook(id int64) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
diff --git a/modules/webhook/webhook.go b/modules/webhook/webhook.go
index 2fab0803bc..75a81d2aff 100644
--- a/modules/webhook/webhook.go
+++ b/modules/webhook/webhook.go
@@ -181,6 +181,13 @@ func prepareWebhooks(repo *models.Repository, event models.HookEventType, p api.
ws = append(ws, orgHooks...)
}
+ // Add any admin-defined system webhooks
+ systemHooks, err := models.GetSystemWebhooks()
+ if err != nil {
+ return fmt.Errorf("GetSystemWebhooks: %v", err)
+ }
+ ws = append(ws, systemHooks...)
+
if len(ws) == 0 {
return nil
}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index bf5b03d47d..483970e032 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1753,6 +1753,7 @@ users = User Accounts
organizations = Organizations
repositories = Repositories
hooks = Default Webhooks
+systemhooks = System Webhooks
authentication = Authentication Sources
emails = User Emails
config = Configuration
@@ -1889,6 +1890,10 @@ hooks.desc = Webhooks automatically make HTTP POST requests to a server when cer
hooks.add_webhook = Add Default Webhook
hooks.update_webhook = Update Default Webhook
+systemhooks.desc = Webhooks automatically make HTTP POST requests to a server when certain Gitea events trigger. Webhooks defined will act on all repositories on the system, so please consider any performance implications this may have. Read more in the webhooks guide.
+systemhooks.add_webhook = Add System Webhook
+systemhooks.update_webhook = Update System Webhook
+
auths.auth_manage_panel = Authentication Source Management
auths.new = Add Authentication Source
auths.name = Name
diff --git a/routers/admin/hooks.go b/routers/admin/hooks.go
index b80ed3cc3c..4697c4d933 100644
--- a/routers/admin/hooks.go
+++ b/routers/admin/hooks.go
@@ -12,20 +12,32 @@ import (
)
const (
- // tplAdminHooks template path for render hook settings
+ // tplAdminHooks template path to render hook settings
tplAdminHooks base.TplName = "admin/hooks"
)
-// DefaultWebhooks render admin-default webhook list page
-func DefaultWebhooks(ctx *context.Context) {
- ctx.Data["Title"] = ctx.Tr("admin.hooks")
- ctx.Data["PageIsAdminHooks"] = true
- ctx.Data["BaseLink"] = setting.AppSubURL + "/admin/hooks"
- ctx.Data["Description"] = ctx.Tr("admin.hooks.desc")
+// DefaultOrSystemWebhooks renders both admin default and system webhook list pages
+func DefaultOrSystemWebhooks(ctx *context.Context) {
+ var ws []*models.Webhook
+ var err error
+
+ // Are we looking at default webhooks?
+ if ctx.Params(":configType") == "hooks" {
+ ctx.Data["Title"] = ctx.Tr("admin.hooks")
+ ctx.Data["Description"] = ctx.Tr("admin.hooks.desc")
+ ctx.Data["PageIsAdminHooks"] = true
+ ctx.Data["BaseLink"] = setting.AppSubURL + "/admin/hooks"
+ ws, err = models.GetDefaultWebhooks()
+ } else {
+ ctx.Data["Title"] = ctx.Tr("admin.systemhooks")
+ ctx.Data["Description"] = ctx.Tr("admin.systemhooks.desc")
+ ctx.Data["PageIsAdminSystemHooks"] = true
+ ctx.Data["BaseLink"] = setting.AppSubURL + "/admin/system-hooks"
+ ws, err = models.GetSystemWebhooks()
+ }
- ws, err := models.GetDefaultWebhooks()
if err != nil {
- ctx.ServerError("GetWebhooksDefaults", err)
+ ctx.ServerError("GetWebhooksAdmin", err)
return
}
@@ -33,15 +45,22 @@ func DefaultWebhooks(ctx *context.Context) {
ctx.HTML(200, tplAdminHooks)
}
-// DeleteDefaultWebhook response for delete admin-default webhook
-func DeleteDefaultWebhook(ctx *context.Context) {
- if err := models.DeleteDefaultWebhook(ctx.QueryInt64("id")); err != nil {
+// DeleteDefaultOrSystemWebhook handler to delete an admin-defined system or default webhook
+func DeleteDefaultOrSystemWebhook(ctx *context.Context) {
+ if err := models.DeleteDefaultSystemWebhook(ctx.QueryInt64("id")); err != nil {
ctx.Flash.Error("DeleteDefaultWebhook: " + err.Error())
} else {
ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success"))
}
- ctx.JSON(200, map[string]interface{}{
- "redirect": setting.AppSubURL + "/admin/hooks",
- })
+ // Are we looking at default webhooks?
+ if ctx.Params(":configType") == "hooks" {
+ ctx.JSON(200, map[string]interface{}{
+ "redirect": setting.AppSubURL + "/admin/hooks",
+ })
+ } else {
+ ctx.JSON(200, map[string]interface{}{
+ "redirect": setting.AppSubURL + "/admin/system-hooks",
+ })
+ }
}
diff --git a/routers/repo/webhook.go b/routers/repo/webhook.go
index cf6ff27542..94c610fe54 100644
--- a/routers/repo/webhook.go
+++ b/routers/repo/webhook.go
@@ -49,14 +49,15 @@ func Webhooks(ctx *context.Context) {
}
type orgRepoCtx struct {
- OrgID int64
- RepoID int64
- IsAdmin bool
- Link string
- NewTemplate base.TplName
+ OrgID int64
+ RepoID int64
+ IsAdmin bool
+ IsSystemWebhook bool
+ Link string
+ NewTemplate base.TplName
}
-// getOrgRepoCtx determines whether this is a repo, organization, or admin context.
+// getOrgRepoCtx determines whether this is a repo, organization, or admin (both default and system) context.
func getOrgRepoCtx(ctx *context.Context) (*orgRepoCtx, error) {
if len(ctx.Repo.RepoLink) > 0 {
return &orgRepoCtx{
@@ -75,10 +76,21 @@ func getOrgRepoCtx(ctx *context.Context) (*orgRepoCtx, error) {
}
if ctx.User.IsAdmin {
+ // Are we looking at default webhooks?
+ if ctx.Params(":configType") == "hooks" {
+ return &orgRepoCtx{
+ IsAdmin: true,
+ Link: path.Join(setting.AppSubURL, "/admin/hooks"),
+ NewTemplate: tplAdminHookNew,
+ }, nil
+ }
+
+ // Must be system webhooks instead
return &orgRepoCtx{
- IsAdmin: true,
- Link: path.Join(setting.AppSubURL, "/admin/hooks"),
- NewTemplate: tplAdminHookNew,
+ IsAdmin: true,
+ IsSystemWebhook: true,
+ Link: path.Join(setting.AppSubURL, "/admin/system-hooks"),
+ NewTemplate: tplAdminHookNew,
}, nil
}
@@ -105,7 +117,10 @@ func WebhooksNew(ctx *context.Context) {
return
}
- if orCtx.IsAdmin {
+ if orCtx.IsAdmin && orCtx.IsSystemWebhook {
+ ctx.Data["PageIsAdminSystemHooks"] = true
+ ctx.Data["PageIsAdminSystemHooksNew"] = true
+ } else if orCtx.IsAdmin {
ctx.Data["PageIsAdminHooks"] = true
ctx.Data["PageIsAdminHooksNew"] = true
} else {
@@ -159,8 +174,8 @@ func ParseHookEvent(form auth.WebhookForm) *models.HookEvent {
}
}
-// WebHooksNewPost response for creating webhook
-func WebHooksNewPost(ctx *context.Context, form auth.NewWebhookForm) {
+// GiteaHooksNewPost response for creating Gitea webhook
+func GiteaHooksNewPost(ctx *context.Context, form auth.NewWebhookForm) {
ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true
@@ -185,15 +200,16 @@ func WebHooksNewPost(ctx *context.Context, form auth.NewWebhookForm) {
}
w := &models.Webhook{
- RepoID: orCtx.RepoID,
- URL: form.PayloadURL,
- HTTPMethod: form.HTTPMethod,
- ContentType: contentType,
- Secret: form.Secret,
- HookEvent: ParseHookEvent(form.WebhookForm),
- IsActive: form.Active,
- HookTaskType: models.GITEA,
- OrgID: orCtx.OrgID,
+ RepoID: orCtx.RepoID,
+ URL: form.PayloadURL,
+ HTTPMethod: form.HTTPMethod,
+ ContentType: contentType,
+ Secret: form.Secret,
+ HookEvent: ParseHookEvent(form.WebhookForm),
+ IsActive: form.Active,
+ HookTaskType: models.GITEA,
+ OrgID: orCtx.OrgID,
+ IsSystemWebhook: orCtx.IsSystemWebhook,
}
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
@@ -238,14 +254,15 @@ func newGogsWebhookPost(ctx *context.Context, form auth.NewGogshookForm, kind mo
}
w := &models.Webhook{
- RepoID: orCtx.RepoID,
- URL: form.PayloadURL,
- ContentType: contentType,
- Secret: form.Secret,
- HookEvent: ParseHookEvent(form.WebhookForm),
- IsActive: form.Active,
- HookTaskType: kind,
- OrgID: orCtx.OrgID,
+ RepoID: orCtx.RepoID,
+ URL: form.PayloadURL,
+ ContentType: contentType,
+ Secret: form.Secret,
+ HookEvent: ParseHookEvent(form.WebhookForm),
+ IsActive: form.Active,
+ HookTaskType: kind,
+ OrgID: orCtx.OrgID,
+ IsSystemWebhook: orCtx.IsSystemWebhook,
}
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
@@ -287,14 +304,15 @@ func DiscordHooksNewPost(ctx *context.Context, form auth.NewDiscordHookForm) {
}
w := &models.Webhook{
- RepoID: orCtx.RepoID,
- URL: form.PayloadURL,
- ContentType: models.ContentTypeJSON,
- HookEvent: ParseHookEvent(form.WebhookForm),
- IsActive: form.Active,
- HookTaskType: models.DISCORD,
- Meta: string(meta),
- OrgID: orCtx.OrgID,
+ RepoID: orCtx.RepoID,
+ URL: form.PayloadURL,
+ ContentType: models.ContentTypeJSON,
+ HookEvent: ParseHookEvent(form.WebhookForm),
+ IsActive: form.Active,
+ HookTaskType: models.DISCORD,
+ Meta: string(meta),
+ OrgID: orCtx.OrgID,
+ IsSystemWebhook: orCtx.IsSystemWebhook,
}
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
@@ -327,14 +345,15 @@ func DingtalkHooksNewPost(ctx *context.Context, form auth.NewDingtalkHookForm) {
}
w := &models.Webhook{
- RepoID: orCtx.RepoID,
- URL: form.PayloadURL,
- ContentType: models.ContentTypeJSON,
- HookEvent: ParseHookEvent(form.WebhookForm),
- IsActive: form.Active,
- HookTaskType: models.DINGTALK,
- Meta: "",
- OrgID: orCtx.OrgID,
+ RepoID: orCtx.RepoID,
+ URL: form.PayloadURL,
+ ContentType: models.ContentTypeJSON,
+ HookEvent: ParseHookEvent(form.WebhookForm),
+ IsActive: form.Active,
+ HookTaskType: models.DINGTALK,
+ Meta: "",
+ OrgID: orCtx.OrgID,
+ IsSystemWebhook: orCtx.IsSystemWebhook,
}
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
@@ -376,14 +395,15 @@ func TelegramHooksNewPost(ctx *context.Context, form auth.NewTelegramHookForm) {
}
w := &models.Webhook{
- RepoID: orCtx.RepoID,
- URL: fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s", form.BotToken, form.ChatID),
- ContentType: models.ContentTypeJSON,
- HookEvent: ParseHookEvent(form.WebhookForm),
- IsActive: form.Active,
- HookTaskType: models.TELEGRAM,
- Meta: string(meta),
- OrgID: orCtx.OrgID,
+ RepoID: orCtx.RepoID,
+ URL: fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s", form.BotToken, form.ChatID),
+ ContentType: models.ContentTypeJSON,
+ HookEvent: ParseHookEvent(form.WebhookForm),
+ IsActive: form.Active,
+ HookTaskType: models.TELEGRAM,
+ Meta: string(meta),
+ OrgID: orCtx.OrgID,
+ IsSystemWebhook: orCtx.IsSystemWebhook,
}
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
@@ -416,14 +436,15 @@ func MSTeamsHooksNewPost(ctx *context.Context, form auth.NewMSTeamsHookForm) {
}
w := &models.Webhook{
- RepoID: orCtx.RepoID,
- URL: form.PayloadURL,
- ContentType: models.ContentTypeJSON,
- HookEvent: ParseHookEvent(form.WebhookForm),
- IsActive: form.Active,
- HookTaskType: models.MSTEAMS,
- Meta: "",
- OrgID: orCtx.OrgID,
+ RepoID: orCtx.RepoID,
+ URL: form.PayloadURL,
+ ContentType: models.ContentTypeJSON,
+ HookEvent: ParseHookEvent(form.WebhookForm),
+ IsActive: form.Active,
+ HookTaskType: models.MSTEAMS,
+ Meta: "",
+ OrgID: orCtx.OrgID,
+ IsSystemWebhook: orCtx.IsSystemWebhook,
}
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
@@ -473,14 +494,15 @@ func SlackHooksNewPost(ctx *context.Context, form auth.NewSlackHookForm) {
}
w := &models.Webhook{
- RepoID: orCtx.RepoID,
- URL: form.PayloadURL,
- ContentType: models.ContentTypeJSON,
- HookEvent: ParseHookEvent(form.WebhookForm),
- IsActive: form.Active,
- HookTaskType: models.SLACK,
- Meta: string(meta),
- OrgID: orCtx.OrgID,
+ RepoID: orCtx.RepoID,
+ URL: form.PayloadURL,
+ ContentType: models.ContentTypeJSON,
+ HookEvent: ParseHookEvent(form.WebhookForm),
+ IsActive: form.Active,
+ HookTaskType: models.SLACK,
+ Meta: string(meta),
+ OrgID: orCtx.OrgID,
+ IsSystemWebhook: orCtx.IsSystemWebhook,
}
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
@@ -513,14 +535,15 @@ func FeishuHooksNewPost(ctx *context.Context, form auth.NewFeishuHookForm) {
}
w := &models.Webhook{
- RepoID: orCtx.RepoID,
- URL: form.PayloadURL,
- ContentType: models.ContentTypeJSON,
- HookEvent: ParseHookEvent(form.WebhookForm),
- IsActive: form.Active,
- HookTaskType: models.FEISHU,
- Meta: "",
- OrgID: orCtx.OrgID,
+ RepoID: orCtx.RepoID,
+ URL: form.PayloadURL,
+ ContentType: models.ContentTypeJSON,
+ HookEvent: ParseHookEvent(form.WebhookForm),
+ IsActive: form.Active,
+ HookTaskType: models.FEISHU,
+ Meta: "",
+ OrgID: orCtx.OrgID,
+ IsSystemWebhook: orCtx.IsSystemWebhook,
}
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
@@ -549,6 +572,8 @@ func checkWebhook(ctx *context.Context) (*orgRepoCtx, *models.Webhook) {
w, err = models.GetWebhookByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
} else if orCtx.OrgID > 0 {
w, err = models.GetWebhookByOrgID(ctx.Org.Organization.ID, ctx.ParamsInt64(":id"))
+ } else if orCtx.IsSystemWebhook {
+ w, err = models.GetSystemWebhook(ctx.ParamsInt64(":id"))
} else {
w, err = models.GetDefaultWebhook(ctx.ParamsInt64(":id"))
}
diff --git a/routers/routes/routes.go b/routers/routes/routes.go
index 0b0b4e05a3..093edcd920 100644
--- a/routers/routes/routes.go
+++ b/routers/routes/routes.go
@@ -458,11 +458,11 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/delete", admin.DeleteRepo)
})
- m.Group("/hooks", func() {
- m.Get("", admin.DefaultWebhooks)
- m.Post("/delete", admin.DeleteDefaultWebhook)
+ m.Group("/^:configType(hooks|system-hooks)$", func() {
+ m.Get("", admin.DefaultOrSystemWebhooks)
+ m.Post("/delete", admin.DeleteDefaultOrSystemWebhook)
m.Get("/:type/new", repo.WebhooksNew)
- m.Post("/gitea/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost)
+ m.Post("/gitea/new", bindIgnErr(auth.NewWebhookForm{}), repo.GiteaHooksNewPost)
m.Post("/gogs/new", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksNewPost)
m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
m.Post("/discord/new", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksNewPost)
@@ -569,7 +569,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("", org.Webhooks)
m.Post("/delete", org.DeleteWebhook)
m.Get("/:type/new", repo.WebhooksNew)
- m.Post("/gitea/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost)
+ m.Post("/gitea/new", bindIgnErr(auth.NewWebhookForm{}), repo.GiteaHooksNewPost)
m.Post("/gogs/new", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksNewPost)
m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
m.Post("/discord/new", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksNewPost)
@@ -635,7 +635,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("", repo.Webhooks)
m.Post("/delete", repo.DeleteWebhook)
m.Get("/:type/new", repo.WebhooksNew)
- m.Post("/gitea/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost)
+ m.Post("/gitea/new", bindIgnErr(auth.NewWebhookForm{}), repo.GiteaHooksNewPost)
m.Post("/gogs/new", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksNewPost)
m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
m.Post("/discord/new", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksNewPost)
diff --git a/templates/admin/navbar.tmpl b/templates/admin/navbar.tmpl
index 546df22e12..6d81d7557f 100644
--- a/templates/admin/navbar.tmpl
+++ b/templates/admin/navbar.tmpl
@@ -14,6 +14,9 @@
{{.i18n.Tr "admin.hooks"}}
+
+ {{.i18n.Tr "admin.systemhooks"}}
+
{{.i18n.Tr "admin.authentication"}}