| @@ -284,9 +284,11 @@ func runWeb(*cli.Context) { | |||
| r.Route("/collaboration", "GET,POST", repo.SettingsCollaboration) | |||
| r.Get("/hooks", repo.Webhooks) | |||
| r.Get("/hooks/new", repo.WebHooksNew) | |||
| r.Post("/hooks/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost) | |||
| r.Post("/hooks/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost) | |||
| r.Post("/hooks/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost) | |||
| r.Get("/hooks/:id", repo.WebHooksEdit) | |||
| r.Post("/hooks/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost) | |||
| r.Post("/hooks/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost) | |||
| r.Post("/hooks/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost) | |||
| }) | |||
| }, reqSignIn, middleware.RepoAssignment(true), reqTrueOwner) | |||
| @@ -234,6 +234,11 @@ settings.update_webhook = Update Webhook | |||
| settings.update_hook_success = Webhook has been updated. | |||
| settings.delete_webhook = Delete Webhook | |||
| settings.recent_deliveries = Recent Deliveries | |||
| settings.hook_type = Hook Type | |||
| settings.add_slack_hook_desc = Add <a href="http://slack.com">Slack</a> integration to your repository. | |||
| settings.slack_token = Token | |||
| settings.slack_domain = Domain | |||
| settings.slack_channel = Channel | |||
| [org] | |||
| org_name_holder = Organization Name | |||
| @@ -266,14 +266,33 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string, | |||
| continue | |||
| } | |||
| p.Secret = w.Secret | |||
| CreateHookTask(&HookTask{ | |||
| Type: WEBHOOK, | |||
| Url: w.Url, | |||
| Payload: p, | |||
| ContentType: w.ContentType, | |||
| IsSsl: w.IsSsl, | |||
| }) | |||
| switch w.HookTaskType { | |||
| case SLACK: | |||
| { | |||
| s, err := GetSlackPayload(p, w.Meta) | |||
| if err != nil { | |||
| return errors.New("action.GetSlackPayload: " + err.Error()) | |||
| } | |||
| CreateHookTask(&HookTask{ | |||
| Type: w.HookTaskType, | |||
| Url: w.Url, | |||
| BasePayload: s, | |||
| ContentType: w.ContentType, | |||
| IsSsl: w.IsSsl, | |||
| }) | |||
| } | |||
| default: | |||
| { | |||
| p.Secret = w.Secret | |||
| CreateHookTask(&HookTask{ | |||
| Type: w.HookTaskType, | |||
| Url: w.Url, | |||
| BasePayload: p, | |||
| ContentType: w.ContentType, | |||
| IsSsl: w.IsSsl, | |||
| }) | |||
| } | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,114 @@ | |||
| // Copyright 2014 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 models | |||
| import ( | |||
| "encoding/json" | |||
| "errors" | |||
| "fmt" | |||
| "strings" | |||
| ) | |||
| const ( | |||
| SLACK_COLOR string = "#dd4b39" | |||
| ) | |||
| type Slack struct { | |||
| Domain string `json:"domain"` | |||
| Token string `json:"token"` | |||
| Channel string `json:"channel"` | |||
| } | |||
| type SlackPayload struct { | |||
| Channel string `json:"channel"` | |||
| Text string `json:"text"` | |||
| Username string `json:"username"` | |||
| IconUrl string `json:"icon_url"` | |||
| UnfurlLinks int `json:"unfurl_links"` | |||
| LinkNames int `json:"link_names"` | |||
| Attachments []SlackAttachment `json:"attachments"` | |||
| } | |||
| type SlackAttachment struct { | |||
| Color string `json:"color"` | |||
| Text string `json:"text"` | |||
| } | |||
| func GetSlackURL(domain string, token string) string { | |||
| return fmt.Sprintf( | |||
| "https://%s.slack.com/services/hooks/incoming-webhook?token=%s", | |||
| domain, | |||
| token, | |||
| ) | |||
| } | |||
| func (p SlackPayload) GetJSONPayload() ([]byte, error) { | |||
| data, err := json.Marshal(p) | |||
| if err != nil { | |||
| return []byte{}, err | |||
| } | |||
| return data, nil | |||
| } | |||
| func GetSlackPayload(p *Payload, meta string) (*SlackPayload, error) { | |||
| slack := &Slack{} | |||
| slackPayload := &SlackPayload{} | |||
| if err := json.Unmarshal([]byte(meta), &slack); err != nil { | |||
| return slackPayload, errors.New("GetSlackPayload meta json:" + err.Error()) | |||
| } | |||
| // TODO: handle different payload types: push, new branch, delete branch etc. | |||
| // when they are added to gogs. Only handles push now | |||
| return getSlackPushPayload(p, slack) | |||
| } | |||
| func getSlackPushPayload(p *Payload, slack *Slack) (*SlackPayload, error) { | |||
| // n new commits | |||
| refSplit := strings.Split(p.Ref, "/") | |||
| branchName := refSplit[len(refSplit)-1] | |||
| var commitString string | |||
| // TODO: add commit compare before/after link when gogs adds it | |||
| if len(p.Commits) == 1 { | |||
| commitString = "1 new commit" | |||
| } else { | |||
| commitString = fmt.Sprintf("%d new commits", len(p.Commits)) | |||
| } | |||
| text := fmt.Sprintf("[%s:%s] %s pushed by %s", p.Repo.Name, branchName, commitString, p.Pusher.Name) | |||
| var attachmentText string | |||
| // for each commit, generate attachment text | |||
| for i, commit := range p.Commits { | |||
| attachmentText += fmt.Sprintf("<%s|%s>: %s - %s", commit.Url, commit.Id[:7], SlackFormatter(commit.Message), commit.Author.Name) | |||
| // add linebreak to each commit but the last | |||
| if i < len(p.Commits)-1 { | |||
| attachmentText += "\n" | |||
| } | |||
| } | |||
| slackAttachments := []SlackAttachment{{Color: SLACK_COLOR, Text: attachmentText}} | |||
| return &SlackPayload{ | |||
| Channel: slack.Channel, | |||
| Text: text, | |||
| Username: "gogs", | |||
| IconUrl: "https://raw.githubusercontent.com/gogits/gogs/master/public/img/favicon.png", | |||
| UnfurlLinks: 0, | |||
| LinkNames: 0, | |||
| Attachments: slackAttachments, | |||
| }, nil | |||
| } | |||
| // see: https://api.slack.com/docs/formatting | |||
| func SlackFormatter(s string) string { | |||
| // take only first line of commit | |||
| first := strings.Split(s, "\n")[0] | |||
| // replace & < > | |||
| first = strings.Replace(first, "&", "&", -1) | |||
| first = strings.Replace(first, "<", "<", -1) | |||
| first = strings.Replace(first, ">", ">", -1) | |||
| return first | |||
| } | |||
| @@ -7,6 +7,7 @@ package models | |||
| import ( | |||
| "encoding/json" | |||
| "errors" | |||
| "io/ioutil" | |||
| "time" | |||
| "github.com/gogits/gogs/modules/httplib" | |||
| @@ -33,15 +34,17 @@ type HookEvent struct { | |||
| // Webhook represents a web hook object. | |||
| type Webhook struct { | |||
| Id int64 | |||
| RepoId int64 | |||
| Url string `xorm:"TEXT"` | |||
| ContentType HookContentType | |||
| Secret string `xorm:"TEXT"` | |||
| Events string `xorm:"TEXT"` | |||
| *HookEvent `xorm:"-"` | |||
| IsSsl bool | |||
| IsActive bool | |||
| Id int64 | |||
| RepoId int64 | |||
| Url string `xorm:"TEXT"` | |||
| ContentType HookContentType | |||
| Secret string `xorm:"TEXT"` | |||
| Events string `xorm:"TEXT"` | |||
| *HookEvent `xorm:"-"` | |||
| IsSsl bool | |||
| IsActive bool | |||
| HookTaskType HookTaskType | |||
| Meta string `xorm:"TEXT"` // store hook-specific attributes | |||
| } | |||
| // GetEvent handles conversion from Events to HookEvent. | |||
| @@ -52,6 +55,14 @@ func (w *Webhook) GetEvent() { | |||
| } | |||
| } | |||
| func (w *Webhook) GetSlackHook() *Slack { | |||
| s := &Slack{} | |||
| if err := json.Unmarshal([]byte(w.Meta), s); err != nil { | |||
| log.Error(4, "webhook.GetSlackHook(%d): %v", w.Id, err) | |||
| } | |||
| return s | |||
| } | |||
| // UpdateEvent handles conversion from HookEvent to Events. | |||
| func (w *Webhook) UpdateEvent() error { | |||
| data, err := json.Marshal(w.HookEvent) | |||
| @@ -119,8 +130,8 @@ func DeleteWebhook(hookId int64) error { | |||
| type HookTaskType int | |||
| const ( | |||
| WEBHOOK HookTaskType = iota + 1 | |||
| SERVICE | |||
| GOGS HookTaskType = iota + 1 | |||
| SLACK | |||
| ) | |||
| type HookEventType string | |||
| @@ -152,6 +163,10 @@ type PayloadRepo struct { | |||
| Private bool `json:"private"` | |||
| } | |||
| type BasePayload interface { | |||
| GetJSONPayload() ([]byte, error) | |||
| } | |||
| // Payload represents a payload information of hook. | |||
| type Payload struct { | |||
| Secret string `json:"secret"` | |||
| @@ -161,25 +176,33 @@ type Payload struct { | |||
| Pusher *PayloadAuthor `json:"pusher"` | |||
| } | |||
| func (p Payload) GetJSONPayload() ([]byte, error) { | |||
| data, err := json.Marshal(p) | |||
| if err != nil { | |||
| return []byte{}, err | |||
| } | |||
| return data, nil | |||
| } | |||
| // HookTask represents a hook task. | |||
| type HookTask struct { | |||
| Id int64 | |||
| Uuid string | |||
| Type HookTaskType | |||
| Url string | |||
| *Payload `xorm:"-"` | |||
| BasePayload `xorm:"-"` | |||
| PayloadContent string `xorm:"TEXT"` | |||
| ContentType HookContentType | |||
| EventType HookEventType | |||
| IsSsl bool | |||
| IsDeliveried bool | |||
| IsDelivered bool | |||
| IsSucceed bool | |||
| } | |||
| // CreateHookTask creates a new hook task, | |||
| // it handles conversion from Payload to PayloadContent. | |||
| func CreateHookTask(t *HookTask) error { | |||
| data, err := json.Marshal(t.Payload) | |||
| data, err := t.BasePayload.GetJSONPayload() | |||
| if err != nil { | |||
| return err | |||
| } | |||
| @@ -198,7 +221,7 @@ func UpdateHookTask(t *HookTask) error { | |||
| // DeliverHooks checks and delivers undelivered hooks. | |||
| func DeliverHooks() { | |||
| timeout := time.Duration(setting.WebhookDeliverTimeout) * time.Second | |||
| x.Where("is_deliveried=?", false).Iterate(new(HookTask), | |||
| x.Where("is_delivered=?", false).Iterate(new(HookTask), | |||
| func(idx int, bean interface{}) error { | |||
| t := bean.(*HookTask) | |||
| req := httplib.Post(t.Url).SetTimeout(timeout, timeout). | |||
| @@ -212,13 +235,36 @@ func DeliverHooks() { | |||
| req.Param("payload", t.PayloadContent) | |||
| } | |||
| t.IsDeliveried = true | |||
| t.IsDelivered = true | |||
| // TODO: record response. | |||
| if _, err := req.Response(); err != nil { | |||
| log.Error(4, "Delivery: %v", err) | |||
| } else { | |||
| t.IsSucceed = true | |||
| switch t.Type { | |||
| case GOGS: | |||
| { | |||
| if _, err := req.Response(); err != nil { | |||
| log.Error(4, "Delivery: %v", err) | |||
| } else { | |||
| t.IsSucceed = true | |||
| } | |||
| } | |||
| case SLACK: | |||
| { | |||
| if res, err := req.Response(); err != nil { | |||
| log.Error(4, "Delivery: %v", err) | |||
| } else { | |||
| defer res.Body.Close() | |||
| contents, err := ioutil.ReadAll(res.Body) | |||
| if err != nil { | |||
| log.Error(4, "%s", err) | |||
| } else { | |||
| if string(contents) != "ok" { | |||
| log.Error(4, "slack failed with: %s", string(contents)) | |||
| } else { | |||
| t.IsSucceed = true | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| if err := UpdateHookTask(t); err != nil { | |||
| @@ -69,17 +69,31 @@ func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs *binding.Errors, l | |||
| // \/ \/ \/ \/ \/ \/ | |||
| type NewWebhookForm struct { | |||
| PayloadUrl string `form:"payload_url" binding:"Required;Url"` | |||
| ContentType string `form:"content_type" binding:"Required"` | |||
| Secret string `form:"secret"` | |||
| PushOnly bool `form:"push_only"` | |||
| Active bool `form:"active"` | |||
| HookTaskType string `form:"hook_type" binding:"Required"` | |||
| PayloadUrl string `form:"payload_url" binding:"Required;Url"` | |||
| ContentType string `form:"content_type" binding:"Required"` | |||
| Secret string `form:"secret"` | |||
| PushOnly bool `form:"push_only"` | |||
| Active bool `form:"active"` | |||
| } | |||
| func (f *NewWebhookForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { | |||
| validate(errs, ctx.Data, f, l) | |||
| } | |||
| type NewSlackHookForm struct { | |||
| HookTaskType string `form:"hook_type" binding:"Required"` | |||
| Domain string `form:"domain" binding:"Required` | |||
| Token string `form:"token" binding:"Required"` | |||
| Channel string `form:"channel" binding:"Required"` | |||
| PushOnly bool `form:"push_only"` | |||
| Active bool `form:"active"` | |||
| } | |||
| func (f *NewSlackHookForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { | |||
| validate(errs, ctx.Data, f, l) | |||
| } | |||
| // .___ | |||
| // | | ______ ________ __ ____ | |||
| // | |/ ___// ___/ | \_/ __ \ | |||
| @@ -1403,14 +1403,16 @@ The register and sign-in page style | |||
| #auth-setting-form, | |||
| #org-setting-form, | |||
| #repo-setting-form, | |||
| #user-profile-form { | |||
| #user-profile-form, | |||
| .repo-setting-form { | |||
| background-color: #FFF; | |||
| padding: 30px 0; | |||
| } | |||
| #auth-setting-form textarea, | |||
| #org-setting-form textarea, | |||
| #repo-setting-form textarea, | |||
| #user-profile-form textarea { | |||
| #user-profile-form textarea, | |||
| .repo-setting-form textarea { | |||
| margin-left: 4px; | |||
| height: 100px; | |||
| } | |||
| @@ -1418,24 +1420,38 @@ The register and sign-in page style | |||
| #org-setting-form label, | |||
| #repo-setting-form label, | |||
| #user-profile-form label, | |||
| .repo-setting-form label, | |||
| #auth-setting-form .form-label, | |||
| #org-setting-form .form-label, | |||
| #repo-setting-form .form-label, | |||
| #user-profile-form .form-label { | |||
| #user-profile-form .form-label, | |||
| .repo-setting-form .form-label { | |||
| width: 240px; | |||
| } | |||
| #auth-setting-form .ipt, | |||
| #org-setting-form .ipt, | |||
| #repo-setting-form .ipt, | |||
| #user-profile-form .ipt { | |||
| #user-profile-form .ipt, | |||
| .repo-setting-form .ipt { | |||
| width: 360px; | |||
| } | |||
| #auth-setting-form .field, | |||
| #org-setting-form .field, | |||
| #repo-setting-form .field, | |||
| #user-profile-form .field { | |||
| #user-profile-form .field, | |||
| .repo-setting-form .field { | |||
| margin-bottom: 24px; | |||
| } | |||
| #hook-type { | |||
| padding: 10px 0 0 0; | |||
| background-color: #fff; | |||
| } | |||
| #hook-type .field { | |||
| margin-bottom: 24px; | |||
| } | |||
| #hook-type label { | |||
| width: 240px; | |||
| } | |||
| #repo-hooks-panel, | |||
| #repo-hooks-history-panel, | |||
| #user-social-panel, | |||
| @@ -359,6 +359,22 @@ function initRepoSetting() { | |||
| return true; | |||
| } | |||
| }); | |||
| // web hook type change | |||
| $('select#hook-type').on("change", function () { | |||
| hookTypes = ['Gogs','Slack']; | |||
| var curHook = $(this).val(); | |||
| hookTypes.forEach(function(hookType) { | |||
| if (curHook === hookType) { | |||
| $('div#'+hookType.toLowerCase()).toggleShow(); | |||
| } | |||
| else { | |||
| $('div#'+hookType.toLowerCase()).toggleHide(); | |||
| } | |||
| }); | |||
| }); | |||
| $('#transfer-button').click(function () { | |||
| $('#transfer-form').show(); | |||
| }); | |||
| @@ -594,4 +610,4 @@ function homepage() { | |||
| } | |||
| $('#promo-form').attr('action', '/user/sign_up'); | |||
| }); | |||
| } | |||
| } | |||
| @@ -34,7 +34,8 @@ | |||
| #auth-setting-form, | |||
| #org-setting-form, | |||
| #repo-setting-form, | |||
| #user-profile-form { | |||
| #user-profile-form, | |||
| .repo-setting-form { | |||
| background-color: #FFF; | |||
| padding: 30px 0; | |||
| textarea { | |||
| @@ -53,6 +54,17 @@ | |||
| } | |||
| } | |||
| #hook-type { | |||
| padding: 10px 0 0 0; | |||
| background-color: #fff; | |||
| .field { | |||
| margin-bottom: 24px; | |||
| } | |||
| label { | |||
| width: 240px; | |||
| } | |||
| } | |||
| #repo-hooks-panel, | |||
| #repo-hooks-history-panel, | |||
| #user-social-panel, | |||
| @@ -109,4 +121,4 @@ | |||
| .field { | |||
| margin-bottom: 24px; | |||
| } | |||
| } | |||
| } | |||
| @@ -5,6 +5,7 @@ | |||
| package repo | |||
| import ( | |||
| "encoding/json" | |||
| "fmt" | |||
| "strings" | |||
| "time" | |||
| @@ -272,11 +273,17 @@ func Webhooks(ctx *middleware.Context) { | |||
| ctx.HTML(200, HOOKS) | |||
| } | |||
| func renderHookTypes(ctx *middleware.Context) { | |||
| ctx.Data["HookTypes"] = []string{"Gogs", "Slack"} | |||
| ctx.Data["HookType"] = "Gogs" | |||
| } | |||
| func WebHooksNew(ctx *middleware.Context) { | |||
| ctx.Data["Title"] = ctx.Tr("repo.settings") | |||
| ctx.Data["PageIsSettingsHooks"] = true | |||
| ctx.Data["PageIsSettingsHooksNew"] = true | |||
| ctx.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}} | |||
| renderHookTypes(ctx) | |||
| ctx.HTML(200, HOOK_NEW) | |||
| } | |||
| @@ -304,8 +311,11 @@ func WebHooksNewPost(ctx *middleware.Context, form auth.NewWebhookForm) { | |||
| HookEvent: &models.HookEvent{ | |||
| PushOnly: form.PushOnly, | |||
| }, | |||
| IsActive: form.Active, | |||
| IsActive: form.Active, | |||
| HookTaskType: models.GOGS, | |||
| Meta: "", | |||
| } | |||
| if err := w.UpdateEvent(); err != nil { | |||
| ctx.Handle(500, "UpdateEvent", err) | |||
| return | |||
| @@ -338,6 +348,19 @@ func WebHooksEdit(ctx *middleware.Context) { | |||
| } | |||
| return | |||
| } | |||
| // set data per HookTaskType | |||
| switch w.HookTaskType { | |||
| case models.SLACK: | |||
| { | |||
| ctx.Data["SlackHook"] = w.GetSlackHook() | |||
| ctx.Data["HookType"] = "slack" | |||
| } | |||
| default: | |||
| { | |||
| ctx.Data["HookType"] = "gogs" | |||
| } | |||
| } | |||
| w.GetEvent() | |||
| ctx.Data["Webhook"] = w | |||
| ctx.HTML(200, HOOK_NEW) | |||
| @@ -394,3 +417,104 @@ func WebHooksEditPost(ctx *middleware.Context, form auth.NewWebhookForm) { | |||
| ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success")) | |||
| ctx.Redirect(fmt.Sprintf("%s/settings/hooks/%d", ctx.Repo.RepoLink, hookId)) | |||
| } | |||
| func SlackHooksNewPost(ctx *middleware.Context, form auth.NewSlackHookForm) { | |||
| ctx.Data["Title"] = ctx.Tr("repo.settings") | |||
| ctx.Data["PageIsSettingsHooks"] = true | |||
| ctx.Data["PageIsSettingsHooksNew"] = true | |||
| ctx.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}} | |||
| if ctx.HasError() { | |||
| ctx.HTML(200, HOOK_NEW) | |||
| return | |||
| } | |||
| meta, err := json.Marshal(&models.Slack{ | |||
| Domain: form.Domain, | |||
| Channel: form.Channel, | |||
| Token: form.Token, | |||
| }) | |||
| if err != nil { | |||
| ctx.Handle(500, "SlackHooksNewPost: JSON marshal failed: ", err) | |||
| return | |||
| } | |||
| w := &models.Webhook{ | |||
| RepoId: ctx.Repo.Repository.Id, | |||
| Url: models.GetSlackURL(form.Domain, form.Token), | |||
| ContentType: models.JSON, | |||
| Secret: "", | |||
| HookEvent: &models.HookEvent{ | |||
| PushOnly: form.PushOnly, | |||
| }, | |||
| IsActive: form.Active, | |||
| HookTaskType: models.SLACK, | |||
| Meta: string(meta), | |||
| } | |||
| if err := w.UpdateEvent(); err != nil { | |||
| ctx.Handle(500, "UpdateEvent", err) | |||
| return | |||
| } else if err := models.CreateWebhook(w); err != nil { | |||
| ctx.Handle(500, "CreateWebhook", err) | |||
| return | |||
| } | |||
| ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) | |||
| ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks") | |||
| } | |||
| func SlackHooksEditPost(ctx *middleware.Context, form auth.NewSlackHookForm) { | |||
| ctx.Data["Title"] = ctx.Tr("repo.settings") | |||
| ctx.Data["PageIsSettingsHooks"] = true | |||
| ctx.Data["PageIsSettingsHooksEdit"] = true | |||
| hookId := com.StrTo(ctx.Params(":id")).MustInt64() | |||
| fmt.Println("hookId slack=%d", hookId) | |||
| if hookId == 0 { | |||
| ctx.Handle(404, "setting.WebHooksEditPost", nil) | |||
| return | |||
| } | |||
| w, err := models.GetWebhookById(hookId) | |||
| if err != nil { | |||
| if err == models.ErrWebhookNotExist { | |||
| ctx.Handle(404, "GetWebhookById", nil) | |||
| } else { | |||
| ctx.Handle(500, "GetWebhookById", err) | |||
| } | |||
| return | |||
| } | |||
| w.GetEvent() | |||
| ctx.Data["Webhook"] = w | |||
| if ctx.HasError() { | |||
| ctx.HTML(200, HOOK_NEW) | |||
| return | |||
| } | |||
| meta, err := json.Marshal(&models.Slack{ | |||
| Domain: form.Domain, | |||
| Channel: form.Channel, | |||
| Token: form.Token, | |||
| }) | |||
| if err != nil { | |||
| ctx.Handle(500, "SlackHooksNewPost: JSON marshal failed: ", err) | |||
| return | |||
| } | |||
| w.Url = models.GetSlackURL(form.Domain, form.Token) | |||
| w.Meta = string(meta) | |||
| w.HookEvent = &models.HookEvent{ | |||
| PushOnly: form.PushOnly, | |||
| } | |||
| w.IsActive = form.Active | |||
| if err := w.UpdateEvent(); err != nil { | |||
| ctx.Handle(500, "UpdateEvent", err) | |||
| return | |||
| } else if err := models.UpdateWebhook(w); err != nil { | |||
| ctx.Handle(500, "SlackHooksEditPost", err) | |||
| return | |||
| } | |||
| ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success")) | |||
| ctx.Redirect(fmt.Sprintf("%s/settings/hooks/%d", ctx.Repo.RepoLink, hookId)) | |||
| } | |||
| @@ -0,0 +1,23 @@ | |||
| <div id="gogs" class="{{if (and .PageIsSettingsHooksEdit (not (eq .HookType "gogs")))}}hidden{{end}}"> | |||
| <form class="form form-align panel-body repo-setting-form" id="repo-setting-form-gogs" action="{{.RepoLink}}/settings/hooks/gogs/{{if .PageIsSettingsHooksNew}}new{{else}}{{.Webhook.Id}}{{end}}" method="post"> | |||
| {{.CsrfTokenHtml}} | |||
| <input type="hidden" name="hook_type" value="gogs"> | |||
| <div class="text-center panel-desc">{{.i18n.Tr "repo.settings.add_webhook_desc" | Str2html}}</div> | |||
| <div class="field"> | |||
| <label class="req" for="payload-url">{{.i18n.Tr "repo.settings.payload_url"}}</label> | |||
| <input class="ipt ipt-large ipt-radius {{if .Err_UserName}}ipt-error{{end}}" id="payload-url" name="payload_url" type="url" value="{{.Webhook.Url}}" required /> | |||
| </div> | |||
| <div class="field"> | |||
| <label class="req">{{.i18n.Tr "repo.settings.content_type"}}</label> | |||
| <select name="content_type"> | |||
| <option value="1" {{if or .PageIsSettingsHooksNew (eq .Webhook.ContentType 1)}}selected{{end}}>application/json</option> | |||
| <option value="2" {{if eq .Webhook.ContentType 2}}selected{{end}}>application/x-www-form-urlencoded</option> | |||
| </select> | |||
| </div> | |||
| <div class="field"> | |||
| <label for="secret">{{.i18n.Tr "repo.settings.secret"}}</label> | |||
| <input class="ipt ipt-large ipt-radius {{if .Err_UserName}}ipt-error{{end}}" id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off" /> | |||
| </div> | |||
| {{template "repo/settings/hook_settings" .}} | |||
| </form> | |||
| </div> | |||
| @@ -13,40 +13,9 @@ | |||
| <div class="panel-header"> | |||
| <strong>{{if .PageIsSettingsHooksNew}}{{.i18n.Tr "repo.settings.add_webhook"}}{{else}}{{.i18n.Tr "repo.settings.update_webhook"}}{{end}}</strong> | |||
| </div> | |||
| <form class="form form-align panel-body" id="repo-setting-form" action="{{.RepoLink}}/settings/hooks/{{if .PageIsSettingsHooksNew}}new{{else}}{{.Webhook.Id}}{{end}}" method="post"> | |||
| {{.CsrfTokenHtml}} | |||
| <div class="text-center panel-desc">{{.i18n.Tr "repo.settings.add_webhook_desc" | Str2html}}</div> | |||
| <div class="field"> | |||
| <label class="req" for="payload-url">{{.i18n.Tr "repo.settings.payload_url"}}</label> | |||
| <input class="ipt ipt-large ipt-radius {{if .Err_UserName}}ipt-error{{end}}" id="payload-url" name="payload_url" type="url" value="{{.Webhook.Url}}" required /> | |||
| </div> | |||
| <div class="field"> | |||
| <label class="req">{{.i18n.Tr "repo.settings.content_type"}}</label> | |||
| <select name="content_type"> | |||
| <option value="1" {{if or .PageIsSettingsHooksNew (eq .Webhook.ContentType 1)}}selected{{end}}>application/json</option> | |||
| <option value="2" {{if eq .Webhook.ContentType 2}}selected{{end}}>application/x-www-form-urlencoded</option> | |||
| </select> | |||
| </div> | |||
| <div class="field"> | |||
| <label for="secret">{{.i18n.Tr "repo.settings.secret"}}</label> | |||
| <input class="ipt ipt-large ipt-radius {{if .Err_UserName}}ipt-error{{end}}" id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off" /> | |||
| </div> | |||
| <div class="field"> | |||
| <h4 class="text-center">{{.i18n.Tr "repo.settings.event_desc"}}</h4> | |||
| <label></label> | |||
| <input name="push_only" type="radio" {{if or .PageIsSettingsHooksNew .Webhook.PushOnly}}checked{{end}}> {{.i18n.Tr "repo.settings.event_push_only" | Str2html}} | |||
| </div> | |||
| <div class="field"> | |||
| <label for="active">{{.i18n.Tr "repo.settings.active"}}</label> | |||
| <input class="ipt-chk" id="active" name="active" type="checkbox" {{if or .PageIsSettingsHooksNew .Webhook.IsActive}}checked{{end}} /> | |||
| <span>{{.i18n.Tr "repo.settings.active_helper"}}</span> | |||
| </div> | |||
| <div class="field"> | |||
| <label></label> | |||
| <button class="btn btn-green btn-large btn-radius">{{if .PageIsSettingsHooksNew}}{{.i18n.Tr "repo.settings.add_webhook"}}{{else}}{{.i18n.Tr "repo.settings.update_webhook"}}{{end}}</button> | |||
| {{if .PageIsSettingsHooksEdit}}<a class="btn btn-red btn-large btn-link btn-radius" href="{{.RepoLink}}/settings/hooks?remove={{.Webhook.Id}}"><strong>{{.i18n.Tr "repo.settings.delete_webhook"}}</strong></a>{{end}} | |||
| </div> | |||
| </form> | |||
| {{template "repo/settings/hook_types" .}} | |||
| {{template "repo/settings/gogs_hook" .}} | |||
| {{template "repo/settings/slack_hook" .}} | |||
| </div> | |||
| </div> | |||
| {{if .PageIsSettingsHooksEdit}} | |||
| @@ -67,4 +36,4 @@ | |||
| </div> | |||
| </div> | |||
| </div> | |||
| {{template "ng/base/footer" .}} | |||
| {{template "ng/base/footer" .}} | |||
| @@ -0,0 +1,15 @@ | |||
| <div class="field"> | |||
| <h4 class="text-center">{{.i18n.Tr "repo.settings.event_desc"}}</h4> | |||
| <label></label> | |||
| <input name="push_only" type="radio" {{if or .PageIsSettingsHooksNew .Webhook.PushOnly}}checked{{end}}> {{.i18n.Tr "repo.settings.event_push_only" | Str2html}} | |||
| </div> | |||
| <div class="field"> | |||
| <label for="active">{{.i18n.Tr "repo.settings.active"}}</label> | |||
| <input class="ipt-chk" id="active" name="active" type="checkbox" {{if or .PageIsSettingsHooksNew .Webhook.IsActive}}checked{{end}} /> | |||
| <span>{{.i18n.Tr "repo.settings.active_helper"}}</span> | |||
| </div> | |||
| <div class="field"> | |||
| <label></label> | |||
| <button class="btn btn-green btn-large btn-radius">{{if .PageIsSettingsHooksNew}}{{.i18n.Tr "repo.settings.add_webhook"}}{{else}}{{.i18n.Tr "repo.settings.update_webhook"}}{{end}}</button> | |||
| {{if .PageIsSettingsHooksEdit}}<a class="btn btn-red btn-large btn-link btn-radius" href="{{.RepoLink}}/settings/hooks?remove={{.Webhook.Id}}"><strong>{{.i18n.Tr "repo.settings.delete_webhook"}}</strong></a>{{end}} | |||
| </div> | |||
| @@ -0,0 +1,11 @@ | |||
| {{if .PageIsSettingsHooksNew}} | |||
| <div id="hook-type" class="form-align"> | |||
| <label class="req">{{.i18n.Tr "repo.settings.hook_type"}}</label> | |||
| <select name="hook_type" id="hook-type" class="form-control"> | |||
| {{if .HookType}}<option value="{{.HookType}}">{{.HookType}}</option>{{end}} | |||
| {{range .HookTypes}} | |||
| {{if not (eq $.HookType .)}}<option value="{{.}}" >{{.}}</option>{{end}} | |||
| {{end}} | |||
| </select> | |||
| </div> | |||
| {{end}} | |||
| @@ -0,0 +1,20 @@ | |||
| <div id="slack" class="{{if or .PageIsSettingsHooksNew (and .PageIsSettingsHooksEdit (not (eq .HookType "slack")))}}hidden{{end}}"> | |||
| <form class="form form-align panel-body repo-setting-form" id="repo-setting-form-slack" action="{{.RepoLink}}/settings/hooks/slack/{{if .PageIsSettingsHooksNew}}new{{else}}{{.Webhook.Id}}{{end}}" method="post"> | |||
| {{.CsrfTokenHtml}} | |||
| <input type="hidden" name="hook_type" value="slack"> | |||
| <div class="text-center panel-desc">{{.i18n.Tr "repo.settings.add_slack_hook_desc" | Str2html}}</div> | |||
| <div class="field"> | |||
| <label class="req" for="domain">{{.i18n.Tr "repo.settings.slack_domain"}}</label> | |||
| <input class="ipt ipt-large ipt-radius {{if .Err_UserName}}ipt-error{{end}}" id="domain" name="domain" type="text" value="{{.SlackHook.Domain}}" placeholde="myslack" required /> | |||
| </div> | |||
| <div class="field"> | |||
| <label class="req" for="token">{{.i18n.Tr "repo.settings.slack_token"}}</label> | |||
| <input class="ipt ipt-large ipt-radius {{if .Err_UserName}}ipt-error{{end}}" id="token" name="token" type="text" value="{{.SlackHook.Token}}" autocomplete="off" required /> | |||
| </div> | |||
| <div class="field"> | |||
| <label class="req" for="channel">{{.i18n.Tr "repo.settings.slack_channel"}}</label> | |||
| <input class="ipt ipt-large ipt-radius {{if .Err_UserName}}ipt-error{{end}}" id="channel" name="channel" type="text" value="{{.SlackHook.Channel}}" placeholder="#general" required /> | |||
| </div> | |||
| {{template "repo/settings/hook_settings" .}} | |||
| </form> | |||
| </div> | |||