| @@ -13,7 +13,7 @@ github.com/go-sql-driver/mysql = commit:a197e5d405 | |||||
| github.com/go-xorm/core = commit:4813c0110d | github.com/go-xorm/core = commit:4813c0110d | ||||
| github.com/go-xorm/xorm = commit:97e7703766 | github.com/go-xorm/xorm = commit:97e7703766 | ||||
| github.com/gogits/chardet = commit:2404f77725 | github.com/gogits/chardet = commit:2404f77725 | ||||
| github.com/gogits/go-gogs-client = commit:92e76d616a | |||||
| github.com/gogits/go-gogs-client = | |||||
| github.com/lib/pq = commit:0dad96c0b9 | github.com/lib/pq = commit:0dad96c0b9 | ||||
| github.com/macaron-contrib/binding = commit:de6ed78668 | github.com/macaron-contrib/binding = commit:de6ed78668 | ||||
| github.com/macaron-contrib/cache = commit:cd824f6f2d | github.com/macaron-contrib/cache = commit:cd824f6f2d | ||||
| @@ -529,7 +529,10 @@ settings.add_webhook_desc = Gogs will send a <code>POST</code> request to the UR | |||||
| settings.payload_url = Payload URL | settings.payload_url = Payload URL | ||||
| settings.content_type = Content Type | settings.content_type = Content Type | ||||
| settings.secret = Secret | settings.secret = Secret | ||||
| settings.event_desc = Upon which events should this webhook be triggered? | |||||
| settings.slack_username = Username | |||||
| settings.slack_icon_url = Icon URL | |||||
| settings.slack_color = Color | |||||
| settings.event_desc = When should this webhook be triggered? | |||||
| settings.event_push_only = Just the <code>push</code> event. | settings.event_push_only = Just the <code>push</code> event. | ||||
| settings.event_send_everything = I need <strong>everything</strong>. | settings.event_send_everything = I need <strong>everything</strong>. | ||||
| settings.event_choose = Let me choose what I need. | settings.event_choose = Let me choose what I need. | ||||
| @@ -325,7 +325,9 @@ repo_lang=仓库语言 | |||||
| repo_lang_helper=请选择 .gitignore 文件 | repo_lang_helper=请选择 .gitignore 文件 | ||||
| license=授权许可 | license=授权许可 | ||||
| license_helper=请选择授权许可文件 | license_helper=请选择授权许可文件 | ||||
| init_readme=使用 README.md 文件初始化仓库 | |||||
| readme=自述文档 | |||||
| readme_helper=请选择自述文档模板 | |||||
| auto_init=使用选定的文件和模板初始化仓库 | |||||
| create_repo=创建仓库 | create_repo=创建仓库 | ||||
| default_branch=默认分支 | default_branch=默认分支 | ||||
| mirror_interval=镜像同步周期(小时) | mirror_interval=镜像同步周期(小时) | ||||
| @@ -527,8 +529,17 @@ settings.add_webhook_desc=我们会通过 <code>POST</code> 请求将订阅事 | |||||
| settings.payload_url=推送地址 | settings.payload_url=推送地址 | ||||
| settings.content_type=数据格式 | settings.content_type=数据格式 | ||||
| settings.secret=密钥文本 | settings.secret=密钥文本 | ||||
| settings.slack_username=服务名称 | |||||
| settings.slack_icon_url=图标 URL | |||||
| settings.slack_color=颜色代码 | |||||
| settings.event_desc=请设置您希望触发 Web 钩子的事件: | settings.event_desc=请设置您希望触发 Web 钩子的事件: | ||||
| settings.event_push_only=只推送 <code>push</code> 事件。 | settings.event_push_only=只推送 <code>push</code> 事件。 | ||||
| settings.event_send_everything=请把 <strong>一切</strong> 都给我 | |||||
| settings.event_choose=我的命运自己主宰 | |||||
| settings.event_create=创建 | |||||
| settings.event_create_desc=创建分支或标签 | |||||
| settings.event_push=推送 | |||||
| settings.event_push_desc=Git 仓库推送 | |||||
| settings.active=是否激活 | settings.active=是否激活 | ||||
| settings.active_helper=当指定事件发生时我们将会触发此 Web 钩子。 | settings.active_helper=当指定事件发生时我们将会触发此 Web 钩子。 | ||||
| settings.add_hook_success=Web 钩子添加成功! | settings.add_hook_success=Web 钩子添加成功! | ||||
| @@ -97,11 +97,16 @@ type Webhook struct { | |||||
| Updated time.Time `xorm:"UPDATED"` | Updated time.Time `xorm:"UPDATED"` | ||||
| } | } | ||||
| // GetEvent handles conversion from Events to HookEvent. | |||||
| func (w *Webhook) GetEvent() { | |||||
| w.HookEvent = &HookEvent{} | |||||
| if err := json.Unmarshal([]byte(w.Events), w.HookEvent); err != nil { | |||||
| log.Error(4, "webhook.GetEvent(%d): %v", w.ID, err) | |||||
| func (w *Webhook) AfterSet(colName string, _ xorm.Cell) { | |||||
| var err error | |||||
| switch colName { | |||||
| case "events": | |||||
| w.HookEvent = &HookEvent{} | |||||
| if err = json.Unmarshal([]byte(w.Events), w.HookEvent); err != nil { | |||||
| log.Error(3, "Unmarshal[%d]: %v", w.ID, err) | |||||
| } | |||||
| case "created": | |||||
| w.Created = regulateTimeZone(w.Created) | |||||
| } | } | ||||
| } | } | ||||
| @@ -137,6 +142,17 @@ func (w *Webhook) HasPushEvent() bool { | |||||
| (w.ChooseEvents && w.HookEvents.Push) | (w.ChooseEvents && w.HookEvents.Push) | ||||
| } | } | ||||
| func (w *Webhook) EventsArray() []string { | |||||
| events := make([]string, 0, 2) | |||||
| if w.HasCreateEvent() { | |||||
| events = append(events, "create") | |||||
| } | |||||
| if w.HasPushEvent() { | |||||
| events = append(events, "push") | |||||
| } | |||||
| return events | |||||
| } | |||||
| // CreateWebhook creates a new web hook. | // CreateWebhook creates a new web hook. | ||||
| func CreateWebhook(w *Webhook) error { | func CreateWebhook(w *Webhook) error { | ||||
| _, err := x.Insert(w) | _, err := x.Insert(w) | ||||
| @@ -382,8 +398,6 @@ func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) err | |||||
| } | } | ||||
| for _, w := range ws { | for _, w := range ws { | ||||
| w.GetEvent() | |||||
| switch event { | switch event { | ||||
| case HOOK_EVENT_CREATE: | case HOOK_EVENT_CREATE: | ||||
| if !w.HasCreateEvent() { | if !w.HasCreateEvent() { | ||||
| @@ -13,22 +13,20 @@ import ( | |||||
| api "github.com/gogits/go-gogs-client" | api "github.com/gogits/go-gogs-client" | ||||
| "github.com/gogits/gogs/modules/git" | "github.com/gogits/gogs/modules/git" | ||||
| "github.com/gogits/gogs/modules/setting" | |||||
| ) | |||||
| const ( | |||||
| SLACK_COLOR string = "#dd4b39" | |||||
| ) | ) | ||||
| type SlackMeta struct { | type SlackMeta struct { | ||||
| Channel string `json:"channel"` | |||||
| Channel string `json:"channel"` | |||||
| Username string `json:"username"` | |||||
| IconURL string `json:"icon_url"` | |||||
| Color string `json:"color"` | |||||
| } | } | ||||
| type SlackPayload struct { | type SlackPayload struct { | ||||
| Channel string `json:"channel"` | Channel string `json:"channel"` | ||||
| Text string `json:"text"` | Text string `json:"text"` | ||||
| Username string `json:"username"` | Username string `json:"username"` | ||||
| IconUrl string `json:"icon_url"` | |||||
| IconURL string `json:"icon_url"` | |||||
| UnfurlLinks int `json:"unfurl_links"` | UnfurlLinks int `json:"unfurl_links"` | ||||
| LinkNames int `json:"link_names"` | LinkNames int `json:"link_names"` | ||||
| Attachments []SlackAttachment `json:"attachments"` | Attachments []SlackAttachment `json:"attachments"` | ||||
| @@ -75,8 +73,8 @@ func getSlackCreatePayload(p *api.CreatePayload, slack *SlackMeta) (*SlackPayloa | |||||
| return &SlackPayload{ | return &SlackPayload{ | ||||
| Channel: slack.Channel, | Channel: slack.Channel, | ||||
| Text: text, | Text: text, | ||||
| Username: setting.AppName, | |||||
| IconUrl: setting.AppUrl + "/img/favicon.png", | |||||
| Username: slack.Username, | |||||
| IconURL: slack.IconURL, | |||||
| }, nil | }, nil | ||||
| } | } | ||||
| @@ -113,13 +111,13 @@ func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, e | |||||
| } | } | ||||
| } | } | ||||
| slackAttachments := []SlackAttachment{{Color: SLACK_COLOR, Text: attachmentText}} | |||||
| slackAttachments := []SlackAttachment{{Color: slack.Color, Text: attachmentText}} | |||||
| return &SlackPayload{ | return &SlackPayload{ | ||||
| Channel: slack.Channel, | Channel: slack.Channel, | ||||
| Text: text, | Text: text, | ||||
| Username: setting.AppName, | |||||
| IconUrl: setting.AppUrl + "/img/favicon.png", | |||||
| Username: slack.Username, | |||||
| IconURL: slack.IconURL, | |||||
| Attachments: slackAttachments, | Attachments: slackAttachments, | ||||
| }, nil | }, nil | ||||
| } | } | ||||
| @@ -99,6 +99,9 @@ func (f *NewWebhookForm) Validate(ctx *macaron.Context, errs binding.Errors) bin | |||||
| type NewSlackHookForm struct { | type NewSlackHookForm struct { | ||||
| PayloadURL string `binding:"Required` | PayloadURL string `binding:"Required` | ||||
| Channel string `binding:"Required"` | Channel string `binding:"Required"` | ||||
| Username string | |||||
| IconURL string | |||||
| Color string | |||||
| WebhookForm | WebhookForm | ||||
| } | } | ||||
| @@ -4,7 +4,7 @@ | |||||
| package base | package base | ||||
| const DOC_URL = "http://gogs.io/docs" | |||||
| const DOC_URL = "https://github.com/gogits/go-gogs-client/wiki" | |||||
| type ( | type ( | ||||
| TplName string | TplName string | ||||
| @@ -6,6 +6,9 @@ package v1 | |||||
| import ( | import ( | ||||
| "encoding/json" | "encoding/json" | ||||
| "fmt" | |||||
| "github.com/Unknwon/com" | |||||
| api "github.com/gogits/go-gogs-client" | api "github.com/gogits/go-gogs-client" | ||||
| @@ -14,8 +17,33 @@ import ( | |||||
| "github.com/gogits/gogs/modules/middleware" | "github.com/gogits/gogs/modules/middleware" | ||||
| ) | ) | ||||
| // GET /repos/:username/:reponame/hooks | |||||
| // https://developer.github.com/v3/repos/hooks/#list-hooks | |||||
| // ToApiHook converts webhook to API format. | |||||
| func ToApiHook(repoLink string, w *models.Webhook) *api.Hook { | |||||
| config := map[string]string{ | |||||
| "url": w.URL, | |||||
| "content_type": w.ContentType.Name(), | |||||
| } | |||||
| if w.HookTaskType == models.SLACK { | |||||
| s := w.GetSlackHook() | |||||
| config["channel"] = s.Channel | |||||
| config["username"] = s.Username | |||||
| config["icon_url"] = s.IconURL | |||||
| config["color"] = s.Color | |||||
| } | |||||
| return &api.Hook{ | |||||
| ID: w.ID, | |||||
| Type: w.HookTaskType.Name(), | |||||
| URL: fmt.Sprintf("%s/settings/hooks/%d", repoLink, w.ID), | |||||
| Active: w.IsActive, | |||||
| Config: config, | |||||
| Events: w.EventsArray(), | |||||
| Updated: w.Updated, | |||||
| Created: w.Created, | |||||
| } | |||||
| } | |||||
| // https://github.com/gogits/go-gogs-client/wiki/Repositories#list-hooks | |||||
| func ListRepoHooks(ctx *middleware.Context) { | func ListRepoHooks(ctx *middleware.Context) { | ||||
| hooks, err := models.GetWebhooksByRepoId(ctx.Repo.Repository.ID) | hooks, err := models.GetWebhooksByRepoId(ctx.Repo.Repository.ID) | ||||
| if err != nil { | if err != nil { | ||||
| @@ -25,31 +53,13 @@ func ListRepoHooks(ctx *middleware.Context) { | |||||
| apiHooks := make([]*api.Hook, len(hooks)) | apiHooks := make([]*api.Hook, len(hooks)) | ||||
| for i := range hooks { | for i := range hooks { | ||||
| h := &api.Hook{ | |||||
| ID: hooks[i].ID, | |||||
| Type: hooks[i].HookTaskType.Name(), | |||||
| Active: hooks[i].IsActive, | |||||
| Config: make(map[string]string), | |||||
| } | |||||
| // Currently, onle have push event. | |||||
| h.Events = []string{"push"} | |||||
| h.Config["url"] = hooks[i].URL | |||||
| h.Config["content_type"] = hooks[i].ContentType.Name() | |||||
| if hooks[i].HookTaskType == models.SLACK { | |||||
| s := hooks[i].GetSlackHook() | |||||
| h.Config["channel"] = s.Channel | |||||
| } | |||||
| apiHooks[i] = h | |||||
| apiHooks[i] = ToApiHook(ctx.Repo.RepoLink, hooks[i]) | |||||
| } | } | ||||
| ctx.JSON(200, &apiHooks) | ctx.JSON(200, &apiHooks) | ||||
| } | } | ||||
| // POST /repos/:username/:reponame/hooks | |||||
| // https://developer.github.com/v3/repos/hooks/#create-a-hook | |||||
| // https://github.com/gogits/go-gogs-client/wiki/Repositories#create-a-hook | |||||
| func CreateRepoHook(ctx *middleware.Context, form api.CreateHookOption) { | func CreateRepoHook(ctx *middleware.Context, form api.CreateHookOption) { | ||||
| if !models.IsValidHookTaskType(form.Type) { | if !models.IsValidHookTaskType(form.Type) { | ||||
| ctx.JSON(422, &base.ApiJsonErr{"invalid hook type", base.DOC_URL}) | ctx.JSON(422, &base.ApiJsonErr{"invalid hook type", base.DOC_URL}) | ||||
| @@ -72,7 +82,11 @@ func CreateRepoHook(ctx *middleware.Context, form api.CreateHookOption) { | |||||
| ContentType: models.ToHookContentType(form.Config["content_type"]), | ContentType: models.ToHookContentType(form.Config["content_type"]), | ||||
| Secret: form.Config["secret"], | Secret: form.Config["secret"], | ||||
| HookEvent: &models.HookEvent{ | HookEvent: &models.HookEvent{ | ||||
| PushOnly: true, // Only support it now. | |||||
| ChooseEvents: true, | |||||
| HookEvents: models.HookEvents{ | |||||
| Create: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_CREATE)), | |||||
| Push: com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_PUSH)), | |||||
| }, | |||||
| }, | }, | ||||
| IsActive: form.Active, | IsActive: form.Active, | ||||
| HookTaskType: models.ToHookTaskType(form.Type), | HookTaskType: models.ToHookTaskType(form.Type), | ||||
| @@ -84,7 +98,10 @@ func CreateRepoHook(ctx *middleware.Context, form api.CreateHookOption) { | |||||
| return | return | ||||
| } | } | ||||
| meta, err := json.Marshal(&models.SlackMeta{ | meta, err := json.Marshal(&models.SlackMeta{ | ||||
| Channel: channel, | |||||
| Channel: channel, | |||||
| Username: form.Config["username"], | |||||
| IconURL: form.Config["icon_url"], | |||||
| Color: form.Config["color"], | |||||
| }) | }) | ||||
| if err != nil { | if err != nil { | ||||
| ctx.JSON(500, &base.ApiJsonErr{"slack: JSON marshal failed: " + err.Error(), base.DOC_URL}) | ctx.JSON(500, &base.ApiJsonErr{"slack: JSON marshal failed: " + err.Error(), base.DOC_URL}) | ||||
| @@ -101,25 +118,10 @@ func CreateRepoHook(ctx *middleware.Context, form api.CreateHookOption) { | |||||
| return | return | ||||
| } | } | ||||
| apiHook := &api.Hook{ | |||||
| ID: w.ID, | |||||
| Type: w.HookTaskType.Name(), | |||||
| Events: []string{"push"}, | |||||
| Active: w.IsActive, | |||||
| Config: map[string]string{ | |||||
| "url": w.URL, | |||||
| "content_type": w.ContentType.Name(), | |||||
| }, | |||||
| } | |||||
| if w.HookTaskType == models.SLACK { | |||||
| s := w.GetSlackHook() | |||||
| apiHook.Config["channel"] = s.Channel | |||||
| } | |||||
| ctx.JSON(201, apiHook) | |||||
| ctx.JSON(201, ToApiHook(ctx.Repo.RepoLink, w)) | |||||
| } | } | ||||
| // PATCH /repos/:username/:reponame/hooks/:id | |||||
| // https://developer.github.com/v3/repos/hooks/#edit-a-hook | |||||
| // https://github.com/gogits/go-gogs-client/wiki/Repositories#edit-a-hook | |||||
| func EditRepoHook(ctx *middleware.Context, form api.EditHookOption) { | func EditRepoHook(ctx *middleware.Context, form api.EditHookOption) { | ||||
| w, err := models.GetWebhookByID(ctx.ParamsInt64(":id")) | w, err := models.GetWebhookByID(ctx.ParamsInt64(":id")) | ||||
| if err != nil { | if err != nil { | ||||
| @@ -142,7 +144,10 @@ func EditRepoHook(ctx *middleware.Context, form api.EditHookOption) { | |||||
| if w.HookTaskType == models.SLACK { | if w.HookTaskType == models.SLACK { | ||||
| if channel, ok := form.Config["channel"]; ok { | if channel, ok := form.Config["channel"]; ok { | ||||
| meta, err := json.Marshal(&models.SlackMeta{ | meta, err := json.Marshal(&models.SlackMeta{ | ||||
| Channel: channel, | |||||
| Channel: channel, | |||||
| Username: form.Config["username"], | |||||
| IconURL: form.Config["icon_url"], | |||||
| Color: form.Config["color"], | |||||
| }) | }) | ||||
| if err != nil { | if err != nil { | ||||
| ctx.JSON(500, &base.ApiJsonErr{"slack: JSON marshal failed: " + err.Error(), base.DOC_URL}) | ctx.JSON(500, &base.ApiJsonErr{"slack: JSON marshal failed: " + err.Error(), base.DOC_URL}) | ||||
| @@ -153,17 +158,25 @@ func EditRepoHook(ctx *middleware.Context, form api.EditHookOption) { | |||||
| } | } | ||||
| } | } | ||||
| // Update events | |||||
| w.PushOnly = false | |||||
| w.SendEverything = false | |||||
| w.ChooseEvents = true | |||||
| w.Create = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_CREATE)) | |||||
| w.Push = com.IsSliceContainsStr(form.Events, string(models.HOOK_EVENT_PUSH)) | |||||
| if err = w.UpdateEvent(); err != nil { | |||||
| ctx.JSON(500, &base.ApiJsonErr{"UpdateEvent: " + err.Error(), base.DOC_URL}) | |||||
| return | |||||
| } | |||||
| if form.Active != nil { | if form.Active != nil { | ||||
| w.IsActive = *form.Active | w.IsActive = *form.Active | ||||
| } | } | ||||
| // FIXME: edit events | |||||
| if err := models.UpdateWebhook(w); err != nil { | if err := models.UpdateWebhook(w); err != nil { | ||||
| ctx.JSON(500, &base.ApiJsonErr{"UpdateWebhook: " + err.Error(), base.DOC_URL}) | ctx.JSON(500, &base.ApiJsonErr{"UpdateWebhook: " + err.Error(), base.DOC_URL}) | ||||
| return | return | ||||
| } | } | ||||
| ctx.JSON(200, map[string]interface{}{ | |||||
| "ok": true, | |||||
| }) | |||||
| ctx.JSON(200, ToApiHook(ctx.Repo.RepoLink, w)) | |||||
| } | } | ||||
| @@ -396,7 +396,10 @@ func SlackHooksNewPost(ctx *middleware.Context, form auth.NewSlackHookForm) { | |||||
| } | } | ||||
| meta, err := json.Marshal(&models.SlackMeta{ | meta, err := json.Marshal(&models.SlackMeta{ | ||||
| Channel: form.Channel, | |||||
| Channel: form.Channel, | |||||
| Username: form.Username, | |||||
| IconURL: form.IconURL, | |||||
| Color: form.Color, | |||||
| }) | }) | ||||
| if err != nil { | if err != nil { | ||||
| ctx.Handle(500, "Marshal", err) | ctx.Handle(500, "Marshal", err) | ||||
| @@ -452,7 +455,6 @@ func checkWebhook(ctx *middleware.Context) (*OrgRepoCtx, *models.Webhook) { | |||||
| default: | default: | ||||
| ctx.Data["HookType"] = "gogs" | ctx.Data["HookType"] = "gogs" | ||||
| } | } | ||||
| w.GetEvent() | |||||
| ctx.Data["History"], err = w.History(1) | ctx.Data["History"], err = w.History(1) | ||||
| if err != nil { | if err != nil { | ||||
| @@ -530,7 +532,10 @@ func SlackHooksEditPost(ctx *middleware.Context, form auth.NewSlackHookForm) { | |||||
| } | } | ||||
| meta, err := json.Marshal(&models.SlackMeta{ | meta, err := json.Marshal(&models.SlackMeta{ | ||||
| Channel: form.Channel, | |||||
| Channel: form.Channel, | |||||
| Username: form.Username, | |||||
| IconURL: form.IconURL, | |||||
| Color: form.Color, | |||||
| }) | }) | ||||
| if err != nil { | if err != nil { | ||||
| ctx.Handle(500, "Marshal", err) | ctx.Handle(500, "Marshal", err) | ||||
| @@ -38,7 +38,7 @@ | |||||
| <div class="inline required field {{if .Err_RepoName}}error{{end}}"> | <div class="inline required field {{if .Err_RepoName}}error{{end}}"> | ||||
| <label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label> | <label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label> | ||||
| <input id="repo_name" name="repo_name" value="{{.repo_name}}" autofocus required> | <input id="repo_name" name="repo_name" value="{{.repo_name}}" autofocus required> | ||||
| <span class="help">{{.i18n.Tr "repo.repo_name_helper"}}</span> | |||||
| <span class="help">{{.i18n.Tr "repo.repo_name_helper" | Safe}}</span> | |||||
| </div> | </div> | ||||
| <div class="inline field"> | <div class="inline field"> | ||||
| <label>{{.i18n.Tr "repo.visibility"}}</label> | <label>{{.i18n.Tr "repo.visibility"}}</label> | ||||
| @@ -8,7 +8,20 @@ | |||||
| </div> | </div> | ||||
| <div class="required field {{if .Err_Channel}}error{{end}}"> | <div class="required field {{if .Err_Channel}}error{{end}}"> | ||||
| <label for="channel">{{.i18n.Tr "repo.settings.slack_channel"}}</label> | <label for="channel">{{.i18n.Tr "repo.settings.slack_channel"}}</label> | ||||
| <input id="channel" name="channel" value="{{.SlackHook.Channel}}" placeholder="#general" required> | |||||
| <input id="channel" name="channel" value="{{.SlackHook.Channel}}" placeholder="e.g. #general" required> | |||||
| </div> | |||||
| <div class="field"> | |||||
| <label for="username">{{.i18n.Tr "repo.settings.slack_username"}}</label> | |||||
| <input id="username" name="username" value="{{.SlackHook.Username}}" placeholder="e.g. Gogs"> | |||||
| </div> | |||||
| <div class="field"> | |||||
| <label for="icon_url">{{.i18n.Tr "repo.settings.slack_icon_url"}}</label> | |||||
| <input id="icon_url" name="icon_url" value="{{.SlackHook.IconURL}}" placeholder="e.g. https://example.com/img/favicon.png"> | |||||
| </div> | |||||
| <div class="field"> | |||||
| <label for="color">{{.i18n.Tr "repo.settings.slack_color"}}</label> | |||||
| <input id="color" name="color" value="{{.SlackHook.Color}}" placeholder="e.g. #dd4b39, good, warning, danger"> | |||||
| </div> | </div> | ||||
| {{template "repo/settings/hook_settings" .}} | {{template "repo/settings/hook_settings" .}} | ||||
| </form> | </form> | ||||