From a9f4489bbcb7b406721582bfc3bbd7e7c7f2a04f Mon Sep 17 00:00:00 2001 From: James Lakin Date: Sun, 8 Mar 2020 22:08:05 +0000 Subject: [PATCH] System-wide webhooks (#10546) * Create system webhook column (and migration) * Create system webhook DB methods Based on the default webhook ones * Modify router to handle system webhooks and default ones * Remove old unused admin nav template * Adjust orgRepoCtx to differentiate system and default webhook URLs * Assign IsSystemWebhook when creating webhooks * Correctly use booleans for IsSystemWebhook * Use system webhooks when preparing webhooks for payload * Add UI and locale changes * Use router params to differentiate admin hook pages * Fix deleting admin webhooks and rename method * Add clarity to webhook docs * Revert "Remove old unused admin nav template" This reverts commit 191a20a7389fe5f6256b0ad6aafd04b9b0e295c5. * Rename WebHooksNewPost to GiteaHooksNewPost for clarity * Reintroduce blank line lost during merge conflict Co-authored-by: Lunny Xiao Co-authored-by: Lauris BH --- docs/content/doc/features/webhooks.en-us.md | 10 +- models/migrations/migrations.go | 2 + models/migrations/v131.go | 22 +++ models/webhook.go | 65 ++++--- modules/webhook/webhook.go | 7 + options/locale/locale_en-US.ini | 5 + routers/admin/hooks.go | 49 ++++-- routers/repo/webhook.go | 179 +++++++++++--------- routers/routes/routes.go | 12 +- templates/admin/navbar.tmpl | 3 + 10 files changed, 232 insertions(+), 122 deletions(-) create mode 100644 models/migrations/v131.go 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"}}