add Slack API webhook support
This commit is contained in:
		
							parent
							
								
									5e6091a30a
								
							
						
					
					
						commit
						2bce24068d
					
				| @ -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 | ||||
| } | ||||
|  | ||||
							
								
								
									
										114
									
								
								models/slack.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								models/slack.go
									
									
									
									
									
										Normal file
									
								
							| @ -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)) | ||||
| } | ||||
|  | ||||
							
								
								
									
										23
									
								
								templates/repo/settings/gogs_hook.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								templates/repo/settings/gogs_hook.tmpl
									
									
									
									
									
										Normal file
									
								
							| @ -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" .}} | ||||
|  | ||||
							
								
								
									
										15
									
								
								templates/repo/settings/hook_settings.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								templates/repo/settings/hook_settings.tmpl
									
									
									
									
									
										Normal file
									
								
							| @ -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> | ||||
							
								
								
									
										11
									
								
								templates/repo/settings/hook_types.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								templates/repo/settings/hook_types.tmpl
									
									
									
									
									
										Normal file
									
								
							| @ -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}} | ||||
							
								
								
									
										20
									
								
								templates/repo/settings/slack_hook.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								templates/repo/settings/slack_hook.tmpl
									
									
									
									
									
										Normal file
									
								
							| @ -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> | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user