]> source.dussan.org Git - gitea.git/commitdiff
add Slack API webhook support
authorChristopher Brickley <brickley@gmail.com>
Sun, 24 Aug 2014 12:59:47 +0000 (08:59 -0400)
committerChristopher Brickley <brickley@gmail.com>
Sun, 31 Aug 2014 16:01:59 +0000 (12:01 -0400)
15 files changed:
cmd/web.go
conf/locale/locale_en-US.ini
models/action.go
models/slack.go [new file with mode: 0644]
models/webhook.go
modules/auth/repo_form.go
public/ng/css/gogs.css
public/ng/js/gogs.js
public/ng/less/gogs/settings.less
routers/repo/setting.go
templates/repo/settings/gogs_hook.tmpl [new file with mode: 0644]
templates/repo/settings/hook_new.tmpl
templates/repo/settings/hook_settings.tmpl [new file with mode: 0644]
templates/repo/settings/hook_types.tmpl [new file with mode: 0644]
templates/repo/settings/slack_hook.tmpl [new file with mode: 0644]

index e0ef3a76a36984b2d1501c6c674fc636d3b8a506..275d3fb90e929a3e4bce5a759bbd33085ad0ed73 100644 (file)
@@ -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)
 
index a99eb92e7ebf4ad2d3f71b088d32db96ff360051..946d5604614f230bc3c3ae12abe89feb9b9a9d34 100644 (file)
@@ -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
index b5f692c49f3892acd515502bfd516d6148320424..d536c84dd0a411c80c4a3c4c27a4e865237e8396 100644 (file)
@@ -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
 }
diff --git a/models/slack.go b/models/slack.go
new file mode 100644 (file)
index 0000000..0a55740
--- /dev/null
@@ -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, "&", "&amp;", -1)
+       first = strings.Replace(first, "<", "&lt;", -1)
+       first = strings.Replace(first, ">", "&gt;", -1)
+       return first
+}
index ced7936646c953cf1669d2c1e61a02ff3575ba3f..55ed4844ed4460288a99ab05295f918a85651fbc 100644 (file)
@@ -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 {
index 3eb0cbc56498fe93f72ab4a3442f43d5bc51851c..5fd1114052213e669e8643adb22c6c7e1c326bc0 100644 (file)
@@ -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)
+}
+
 // .___
 // |   | ______ ________ __   ____
 // |   |/  ___//  ___/  |  \_/ __ \
index d81d6f31493b59e744516fa08430c257e58f086e..0840833755554a8d86312bbac54e175ee9dd5b6d 100644 (file)
@@ -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,
index bade9f342061dd38e6dbd8de0d466e9217a84b45..c08a887a4cf75db6c97bc5dee0bd112ded054ec8 100644 (file)
@@ -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');
     });
-}
\ No newline at end of file
+}
index b246a947eced82e7b718a9db29a4e44da6e92b28..80c00f2dbe6608691787c4b53e6c0ce45b2be0e6 100644 (file)
@@ -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 {
     }
 }
 
+#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,
   .field {
     margin-bottom: 24px;
   }
-}
\ No newline at end of file
+}
index 24c1b13a5ef427c5076a61073a95bc7aa03a16ce..fba9eed6a20fe4284eddcfdea58f51a17e6ac454 100644 (file)
@@ -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))
+}
diff --git a/templates/repo/settings/gogs_hook.tmpl b/templates/repo/settings/gogs_hook.tmpl
new file mode 100644 (file)
index 0000000..678d640
--- /dev/null
@@ -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>
index 2cd0eacb6ad38ebf9ac42b8e91c268f54243b302..7a450282b1fab3b4ba20b7d47087b8bde28d05e5 100644 (file)
                                <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" .}}
\ No newline at end of file
+{{template "ng/base/footer" .}}
diff --git a/templates/repo/settings/hook_settings.tmpl b/templates/repo/settings/hook_settings.tmpl
new file mode 100644 (file)
index 0000000..7bf4e2a
--- /dev/null
@@ -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>
diff --git a/templates/repo/settings/hook_types.tmpl b/templates/repo/settings/hook_types.tmpl
new file mode 100644 (file)
index 0000000..782e2a4
--- /dev/null
@@ -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}}
diff --git a/templates/repo/settings/slack_hook.tmpl b/templates/repo/settings/slack_hook.tmpl
new file mode 100644 (file)
index 0000000..e68571a
--- /dev/null
@@ -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>