From 676bd764fa602048c1a81d94943f45dcb1a75c76 Mon Sep 17 00:00:00 2001 From: Unknwon Date: Sun, 31 Aug 2014 21:03:16 +0800 Subject: UI: SSH/HTTPS address switch and copy --- public/ng/js/gogs.js | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) (limited to 'public/ng/js/gogs.js') diff --git a/public/ng/js/gogs.js b/public/ng/js/gogs.js index 9fe06555..bade9f34 100644 --- a/public/ng/js/gogs.js +++ b/public/ng/js/gogs.js @@ -238,6 +238,44 @@ var Gogs = {}; } }); } + + // Copy util. + Gogs.bindCopy = function (selector) { + if ($(selector).hasClass('js-copy-bind')) { + return; + } + $(selector).zclip({ + path: "/js/ZeroClipboard.swf", + copy: function () { + var t = $(this).data("copy-val"); + var to = $($(this).data("copy-from")); + var str = ""; + if (t == "txt") { + str = to.text(); + } + if (t == 'val') { + str = to.val(); + } + if (t == 'html') { + str = to.html(); + } + return str; + }, + afterCopy: function () { + alert("Clone URL has copied!"); +// var $this = $(this); +// $this.tooltip('hide') +// .attr('data-original-title', 'Copied OK'); +// setTimeout(function () { +// $this.tooltip("show"); +// }, 200); +// setTimeout(function () { +// $this.tooltip('hide') +// .attr('data-original-title', 'Copy to Clipboard'); +// }, 3000); + } + }).addClass("js-copy-bind"); + } })(jQuery); function initCore() { @@ -291,6 +329,26 @@ function initRepoCreate() { console.log('initRepoCreate'); } +function initRepo() { + // Clone link switch button. + $('#repo-clone-ssh').click(function () { + $(this).removeClass('btn-gray').addClass('btn-blue'); + $('#repo-clone-https').removeClass('btn-blue').addClass('btn-gray'); + $('#repo-clone-url').val($(this).data('link')); + $('.clone-url').text($(this).data('link')) + }); + $('#repo-clone-https').click(function () { + $(this).removeClass('btn-gray').addClass('btn-blue'); + $('#repo-clone-ssh').removeClass('btn-blue').addClass('btn-gray'); + $('#repo-clone-url').val($(this).data('link')); + $('.clone-url').text($(this).data('link')) + }); + // Copy URL. + $('#repo-clone-copy').hover(function () { + Gogs.bindCopy($(this)); + }) +} + function initRepoSetting() { // Options. // Confirmation of changing repository name. @@ -481,6 +539,9 @@ $(document).ready(function () { if ($('#repo-create-form').length || $('#repo-migrate-form').length) { initRepoCreate(); } + if ($('#repo-header').length) { + initRepo(); + } if ($('#repo-setting').length) { initRepoSetting(); } -- cgit v1.2.3 From 2bce24068dc3c64ee5e501c48b7f080c48383970 Mon Sep 17 00:00:00 2001 From: Christopher Brickley Date: Sun, 24 Aug 2014 08:59:47 -0400 Subject: add Slack API webhook support --- cmd/web.go | 6 +- conf/locale/locale_en-US.ini | 5 ++ models/action.go | 35 ++++++-- models/slack.go | 114 ++++++++++++++++++++++++++ models/webhook.go | 86 +++++++++++++++----- modules/auth/repo_form.go | 24 ++++-- public/ng/css/gogs.css | 26 ++++-- public/ng/js/gogs.js | 18 ++++- public/ng/less/gogs/settings.less | 16 +++- routers/repo/setting.go | 126 ++++++++++++++++++++++++++++- templates/repo/settings/gogs_hook.tmpl | 23 ++++++ templates/repo/settings/hook_new.tmpl | 39 +-------- templates/repo/settings/hook_settings.tmpl | 15 ++++ templates/repo/settings/hook_types.tmpl | 11 +++ templates/repo/settings/slack_hook.tmpl | 20 +++++ 15 files changed, 485 insertions(+), 79 deletions(-) create mode 100644 models/slack.go create mode 100644 templates/repo/settings/gogs_hook.tmpl create mode 100644 templates/repo/settings/hook_settings.tmpl create mode 100644 templates/repo/settings/hook_types.tmpl create mode 100644 templates/repo/settings/slack_hook.tmpl (limited to 'public/ng/js/gogs.js') diff --git a/cmd/web.go b/cmd/web.go index e0ef3a76..275d3fb9 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -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) diff --git a/conf/locale/locale_en-US.ini b/conf/locale/locale_en-US.ini index a99eb92e..946d5604 100644 --- a/conf/locale/locale_en-US.ini +++ b/conf/locale/locale_en-US.ini @@ -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 Slack integration to your repository. +settings.slack_token = Token +settings.slack_domain = Domain +settings.slack_channel = Channel [org] org_name_holder = Organization Name diff --git a/models/action.go b/models/action.go index b5f692c4..d536c84d 100644 --- a/models/action.go +++ b/models/action.go @@ -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 index 00000000..0a557409 --- /dev/null +++ b/models/slack.go @@ -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 +} diff --git a/models/webhook.go b/models/webhook.go index ced79366..55ed4844 100644 --- a/models/webhook.go +++ b/models/webhook.go @@ -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 { diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index 3eb0cbc5..5fd11140 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -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) +} + // .___ // | | ______ ________ __ ____ // | |/ ___// ___/ | \_/ __ \ diff --git a/public/ng/css/gogs.css b/public/ng/css/gogs.css index d81d6f31..08408337 100644 --- a/public/ng/css/gogs.css +++ b/public/ng/css/gogs.css @@ -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, diff --git a/public/ng/js/gogs.js b/public/ng/js/gogs.js index bade9f34..c08a887a 100644 --- a/public/ng/js/gogs.js +++ b/public/ng/js/gogs.js @@ -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 +} diff --git a/public/ng/less/gogs/settings.less b/public/ng/less/gogs/settings.less index b246a947..80c00f2d 100644 --- a/public/ng/less/gogs/settings.less +++ b/public/ng/less/gogs/settings.less @@ -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; } -} \ No newline at end of file +} diff --git a/routers/repo/setting.go b/routers/repo/setting.go index 24c1b13a..fba9eed6 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -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 index 00000000..678d640b --- /dev/null +++ b/templates/repo/settings/gogs_hook.tmpl @@ -0,0 +1,23 @@ +
+
+ {{.CsrfTokenHtml}} + +
{{.i18n.Tr "repo.settings.add_webhook_desc" | Str2html}}
+
+ + +
+
+ + +
+
+ + +
+ {{template "repo/settings/hook_settings" .}} +
+
diff --git a/templates/repo/settings/hook_new.tmpl b/templates/repo/settings/hook_new.tmpl index 2cd0eacb..7a450282 100644 --- a/templates/repo/settings/hook_new.tmpl +++ b/templates/repo/settings/hook_new.tmpl @@ -13,40 +13,9 @@
{{if .PageIsSettingsHooksNew}}{{.i18n.Tr "repo.settings.add_webhook"}}{{else}}{{.i18n.Tr "repo.settings.update_webhook"}}{{end}}
-
- {{.CsrfTokenHtml}} -
{{.i18n.Tr "repo.settings.add_webhook_desc" | Str2html}}
-
- - -
-
- - -
-
- - -
-
-

{{.i18n.Tr "repo.settings.event_desc"}}

- - {{.i18n.Tr "repo.settings.event_push_only" | Str2html}} -
-
- - - {{.i18n.Tr "repo.settings.active_helper"}} -
-
- - - {{if .PageIsSettingsHooksEdit}}{{.i18n.Tr "repo.settings.delete_webhook"}}{{end}} -
-
+ {{template "repo/settings/hook_types" .}} + {{template "repo/settings/gogs_hook" .}} + {{template "repo/settings/slack_hook" .}} {{if .PageIsSettingsHooksEdit}} @@ -67,4 +36,4 @@ -{{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 index 00000000..7bf4e2a3 --- /dev/null +++ b/templates/repo/settings/hook_settings.tmpl @@ -0,0 +1,15 @@ +
+

{{.i18n.Tr "repo.settings.event_desc"}}

+ + {{.i18n.Tr "repo.settings.event_push_only" | Str2html}} +
+
+ + +{{.i18n.Tr "repo.settings.active_helper"}} +
+
+ + + {{if .PageIsSettingsHooksEdit}}{{.i18n.Tr "repo.settings.delete_webhook"}}{{end}} +
diff --git a/templates/repo/settings/hook_types.tmpl b/templates/repo/settings/hook_types.tmpl new file mode 100644 index 00000000..782e2a4e --- /dev/null +++ b/templates/repo/settings/hook_types.tmpl @@ -0,0 +1,11 @@ +{{if .PageIsSettingsHooksNew}} +
+ + +
+{{end}} diff --git a/templates/repo/settings/slack_hook.tmpl b/templates/repo/settings/slack_hook.tmpl new file mode 100644 index 00000000..e68571a0 --- /dev/null +++ b/templates/repo/settings/slack_hook.tmpl @@ -0,0 +1,20 @@ +
+
+ {{.CsrfTokenHtml}} + +
{{.i18n.Tr "repo.settings.add_slack_hook_desc" | Str2html}}
+
+ + +
+
+ + +
+
+ + +
+ {{template "repo/settings/hook_settings" .}} +
+
-- cgit v1.2.3 From 85c35a6b8bb7430568d375d1e792e1417bbd7f4b Mon Sep 17 00:00:00 2001 From: Christopher Brickley Date: Thu, 4 Sep 2014 07:17:00 -0400 Subject: add organization-level webhooks --- cmd/web.go | 7 +++++ conf/locale/locale_en-US.ini | 1 + models/action.go | 16 ++++++++-- models/webhook.go | 13 ++++++++ public/ng/js/gogs.js | 29 ++++++++++------- routers/org/setting.go | 28 +++++++++++++++++ routers/repo/setting.go | 56 ++++++++++++++++++++++++++++----- templates/org/settings/hook_new.tmpl | 37 ++++++++++++++++++++++ templates/org/settings/hooks.tmpl | 38 ++++++++++++++++++++++ templates/org/settings/nav.tmpl | 3 +- templates/repo/settings/hook_gogs.tmpl | 2 +- templates/repo/settings/hook_slack.tmpl | 2 +- 12 files changed, 208 insertions(+), 24 deletions(-) create mode 100644 templates/org/settings/hook_new.tmpl create mode 100644 templates/org/settings/hooks.tmpl (limited to 'public/ng/js/gogs.js') diff --git a/cmd/web.go b/cmd/web.go index 57164683..f7b8d921 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -260,6 +260,13 @@ func runWeb(*cli.Context) { m.Group("/settings", func(r *macaron.Router) { r.Get("", org.Settings) r.Post("", bindIgnErr(auth.UpdateOrgSettingForm{}), org.SettingsPost) + r.Get("/hooks", org.SettingsHooks) + r.Get("/hooks/new", repo.WebHooksNew) + 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/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost) + r.Post("/hooks/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost) r.Route("/delete", "GET,POST", org.SettingsDelete) }) diff --git a/conf/locale/locale_en-US.ini b/conf/locale/locale_en-US.ini index 4f1acdcd..3969074e 100644 --- a/conf/locale/locale_en-US.ini +++ b/conf/locale/locale_en-US.ini @@ -270,6 +270,7 @@ settings.delete = Delete Organization settings.delete_account = Delete This Organization settings.delete_prompt = The operation will delete this organization permanently, and CANNOT be undone! settings.confirm_delete_account = Confirm Deletion +settings.hooks_desc = Add webhooks that will be triggered for all repositories under this organization. members.public = Public members.public_helper = make private diff --git a/models/action.go b/models/action.go index f739fc35..c0992dba 100644 --- a/models/action.go +++ b/models/action.go @@ -220,8 +220,20 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string, ws, err := GetActiveWebhooksByRepoId(repoId) if err != nil { - return errors.New("action.CommitRepoAction(GetWebhooksByRepoId): " + err.Error()) - } else if len(ws) == 0 { + return errors.New("action.CommitRepoAction(GetActiveWebhooksByRepoId): " + err.Error()) + } + + // check if repo belongs to org and append additional webhooks + if repo.Owner.IsOrganization() { + // get hooks for org + orgws, err := GetActiveWebhooksByOrgId(repo.OwnerId) + if err != nil { + return errors.New("action.CommitRepoAction(GetActiveWebhooksByOrgId): " + err.Error()) + } + ws = append(ws, orgws...) + } + + if len(ws) == 0 { return nil } diff --git a/models/webhook.go b/models/webhook.go index 0b7b3a99..5acc83f5 100644 --- a/models/webhook.go +++ b/models/webhook.go @@ -45,6 +45,7 @@ type Webhook struct { IsActive bool HookTaskType HookTaskType Meta string `xorm:"TEXT"` // store hook-specific attributes + OrgId int64 } // GetEvent handles conversion from Events to HookEvent. @@ -120,6 +121,18 @@ func DeleteWebhook(hookId int64) error { return err } +// GetWebhooksByOrgId returns all webhooks for an organization. +func GetWebhooksByOrgId(orgId int64) (ws []*Webhook, err error) { + err = x.Find(&ws, &Webhook{OrgId: orgId}) + return ws, err +} + +// GetActiveWebhooksByOrgId returns all active webhooks for an organization. +func GetActiveWebhooksByOrgId(orgId int64) (ws []*Webhook, err error) { + err = x.Find(&ws, &Webhook{OrgId: orgId, IsActive: true}) + return ws, err +} + // ___ ___ __ ___________ __ // / | \ ____ ____ | | _\__ ___/____ _____| | __ // / ~ \/ _ \ / _ \| |/ / | | \__ \ / ___/ |/ / diff --git a/public/ng/js/gogs.js b/public/ng/js/gogs.js index c08a887a..c60a5cf6 100644 --- a/public/ng/js/gogs.js +++ b/public/ng/js/gogs.js @@ -349,17 +349,8 @@ function initRepo() { }) } -function initRepoSetting() { - // Options. - // Confirmation of changing repository name. - $('#repo-setting-form').submit(function (e) { - var $reponame = $('#repo_name'); - if (($reponame.data('repo-name') != $reponame.val()) && !confirm('Repository name has been changed, do you want to continue?')) { - e.preventDefault(); - return true; - } - }); - +// when user changes hook type, hide/show proper divs +function initHookTypeChange() { // web hook type change $('select#hook-type').on("change", function () { hookTypes = ['Gogs','Slack']; @@ -374,6 +365,20 @@ function initRepoSetting() { } }); }); +} + +function initRepoSetting() { + // Options. + // Confirmation of changing repository name. + $('#repo-setting-form').submit(function (e) { + var $reponame = $('#repo_name'); + if (($reponame.data('repo-name') != $reponame.val()) && !confirm('Repository name has been changed, do you want to continue?')) { + e.preventDefault(); + return true; + } + }); + + initHookTypeChange(); $('#transfer-button').click(function () { $('#transfer-form').show(); @@ -421,6 +426,8 @@ function initOrgSetting() { return true; } }); + + initHookTypeChange(); } function initInvite() { diff --git a/routers/org/setting.go b/routers/org/setting.go index 0ddf0065..f853ef0e 100644 --- a/routers/org/setting.go +++ b/routers/org/setting.go @@ -5,6 +5,7 @@ package org import ( + "github.com/Unknwon/com" "github.com/gogits/gogs/models" "github.com/gogits/gogs/modules/auth" "github.com/gogits/gogs/modules/base" @@ -15,6 +16,7 @@ import ( const ( SETTINGS_OPTIONS base.TplName = "org/settings/options" SETTINGS_DELETE base.TplName = "org/settings/delete" + SETTINGS_HOOKS base.TplName = "org/settings/hooks" ) func Settings(ctx *middleware.Context) { @@ -97,3 +99,29 @@ func SettingsDelete(ctx *middleware.Context) { ctx.HTML(200, SETTINGS_DELETE) } + +func SettingsHooks(ctx *middleware.Context) { + ctx.Data["Title"] = ctx.Tr("org.settings") + ctx.Data["PageIsSettingsHooks"] = true + + // Delete web hook. + remove := com.StrTo(ctx.Query("remove")).MustInt64() + if remove > 0 { + if err := models.DeleteWebhook(remove); err != nil { + ctx.Handle(500, "DeleteWebhook", err) + return + } + ctx.Flash.Success(ctx.Tr("repo.settings.remove_hook_success")) + ctx.Redirect(ctx.Org.OrgLink + "/settings/hooks") + return + } + + ws, err := models.GetWebhooksByOrgId(ctx.Org.Organization.Id) + if err != nil { + ctx.Handle(500, "GetWebhooksByOrgId", err) + return + } + + ctx.Data["Webhooks"] = ws + ctx.HTML(200, SETTINGS_HOOKS) +} diff --git a/routers/repo/setting.go b/routers/repo/setting.go index 74567812..81747d43 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -6,6 +6,7 @@ package repo import ( "encoding/json" + "errors" "fmt" "strings" "time" @@ -26,6 +27,7 @@ const ( COLLABORATION base.TplName = "repo/settings/collaboration" HOOKS base.TplName = "repo/settings/hooks" HOOK_NEW base.TplName = "repo/settings/hook_new" + ORG_HOOK_NEW base.TplName = "org/settings/hook_new" ) func Settings(ctx *middleware.Context) { @@ -284,7 +286,14 @@ func WebHooksNew(ctx *middleware.Context) { ctx.Data["PageIsSettingsHooksNew"] = true ctx.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}} renderHookTypes(ctx) - ctx.HTML(200, HOOK_NEW) + orgId, repoId, _ := getOrgRepoCtx(ctx) + if repoId > 0 { + ctx.HTML(200, HOOK_NEW) + } else if orgId > 0 { + ctx.HTML(200, ORG_HOOK_NEW) + } else { + ctx.Handle(500, "WebHooksEdit(DetermineContext)", errors.New("Can't determine hook context")) + } } func WebHooksNewPost(ctx *middleware.Context, form auth.NewWebhookForm) { @@ -293,6 +302,8 @@ func WebHooksNewPost(ctx *middleware.Context, form auth.NewWebhookForm) { ctx.Data["PageIsSettingsHooksNew"] = true ctx.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}} + orgId, repoId, link := getOrgRepoCtx(ctx) + if ctx.HasError() { ctx.HTML(200, HOOK_NEW) return @@ -304,7 +315,7 @@ func WebHooksNewPost(ctx *middleware.Context, form auth.NewWebhookForm) { } w := &models.Webhook{ - RepoId: ctx.Repo.Repository.Id, + RepoId: repoId, Url: form.PayloadUrl, ContentType: ct, Secret: form.Secret, @@ -314,6 +325,7 @@ func WebHooksNewPost(ctx *middleware.Context, form auth.NewWebhookForm) { IsActive: form.Active, HookTaskType: models.GOGS, Meta: "", + OrgId: orgId, } if err := w.UpdateEvent(); err != nil { @@ -325,7 +337,7 @@ func WebHooksNewPost(ctx *middleware.Context, form auth.NewWebhookForm) { } ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) - ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks") + ctx.Redirect(link + "/settings/hooks") } func WebHooksEdit(ctx *middleware.Context) { @@ -363,7 +375,14 @@ func WebHooksEdit(ctx *middleware.Context) { } w.GetEvent() ctx.Data["Webhook"] = w - ctx.HTML(200, HOOK_NEW) + orgId, repoId, _ := getOrgRepoCtx(ctx) + if repoId > 0 { + ctx.HTML(200, HOOK_NEW) + } else if orgId > 0 { + ctx.HTML(200, ORG_HOOK_NEW) + } else { + ctx.Handle(500, "WebHooksEdit(DetermineContext)", errors.New("Can't determine hook context")) + } } func WebHooksEditPost(ctx *middleware.Context, form auth.NewWebhookForm) { @@ -413,9 +432,10 @@ func WebHooksEditPost(ctx *middleware.Context, form auth.NewWebhookForm) { ctx.Handle(500, "WebHooksEditPost", err) return } + _, _, link := getOrgRepoCtx(ctx) ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success")) - ctx.Redirect(fmt.Sprintf("%s/settings/hooks/%d", ctx.Repo.RepoLink, hookId)) + ctx.Redirect(fmt.Sprintf("%s/settings/hooks/%d", link, hookId)) } func SlackHooksNewPost(ctx *middleware.Context, form auth.NewSlackHookForm) { @@ -428,6 +448,7 @@ func SlackHooksNewPost(ctx *middleware.Context, form auth.NewSlackHookForm) { ctx.HTML(200, HOOK_NEW) return } + orgId, repoId, link := getOrgRepoCtx(ctx) meta, err := json.Marshal(&models.Slack{ Domain: form.Domain, @@ -440,7 +461,7 @@ func SlackHooksNewPost(ctx *middleware.Context, form auth.NewSlackHookForm) { } w := &models.Webhook{ - RepoId: ctx.Repo.Repository.Id, + RepoId: repoId, Url: models.GetSlackURL(form.Domain, form.Token), ContentType: models.JSON, Secret: "", @@ -450,6 +471,7 @@ func SlackHooksNewPost(ctx *middleware.Context, form auth.NewSlackHookForm) { IsActive: form.Active, HookTaskType: models.SLACK, Meta: string(meta), + OrgId: orgId, } if err := w.UpdateEvent(); err != nil { ctx.Handle(500, "UpdateEvent", err) @@ -460,7 +482,7 @@ func SlackHooksNewPost(ctx *middleware.Context, form auth.NewSlackHookForm) { } ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) - ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks") + ctx.Redirect(link + "/settings/hooks") } func SlackHooksEditPost(ctx *middleware.Context, form auth.NewSlackHookForm) { @@ -514,7 +536,25 @@ func SlackHooksEditPost(ctx *middleware.Context, form auth.NewSlackHookForm) { ctx.Handle(500, "SlackHooksEditPost", err) return } + _, _, link := getOrgRepoCtx(ctx) ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success")) - ctx.Redirect(fmt.Sprintf("%s/settings/hooks/%d", ctx.Repo.RepoLink, hookId)) + ctx.Redirect(fmt.Sprintf("%s/settings/hooks/%d", link, hookId)) +} + +func getOrgRepoCtx(ctx *middleware.Context) (int64, int64, string) { + orgId := int64(0) + repoId := int64(0) + link := "" + if _, ok := ctx.Data["RepoLink"]; ok { + repoId = ctx.Repo.Repository.Id + link = ctx.Repo.RepoLink + } + + if _, ok := ctx.Data["OrgLink"]; ok { + orgId = ctx.Org.Organization.Id + link = ctx.Org.OrgLink + } + + return orgId, repoId, link } diff --git a/templates/org/settings/hook_new.tmpl b/templates/org/settings/hook_new.tmpl new file mode 100644 index 00000000..6e7ee536 --- /dev/null +++ b/templates/org/settings/hook_new.tmpl @@ -0,0 +1,37 @@ +{{template "ng/base/head" .}} +{{template "ng/base/header" .}} +{{template "org/base/header" .}} +
+
+ {{template "org/settings/nav" .}} +
+
+ {{template "ng/base/alert" .}} +
+
+
+ {{if .PageIsSettingsHooksNew}}{{.i18n.Tr "repo.settings.add_webhook"}}{{else}}{{.i18n.Tr "repo.settings.update_webhook"}}{{end}} +
+ {{template "repo/settings/hook_types" .}} + {{template "repo/settings/hook_gogs" .}} + {{template "repo/settings/hook_slack" .}} +
+
+ {{if .PageIsSettingsHooksEdit}} +
+
+
+
+ {{.i18n.Tr "repo.settings.recent_deliveries"}} +
+
    +
  • Coming soon!
  • +
+
+
+ {{end}} +
+
+
+
+{{template "ng/base/footer" .}} diff --git a/templates/org/settings/hooks.tmpl b/templates/org/settings/hooks.tmpl new file mode 100644 index 00000000..713cfeb4 --- /dev/null +++ b/templates/org/settings/hooks.tmpl @@ -0,0 +1,38 @@ +{{template "ng/base/head" .}} +{{template "ng/base/header" .}} +{{template "org/base/header" .}} +
+
+ {{template "org/settings/nav" .}} +
+
+ {{template "ng/base/alert" .}} +
+
+
+ {{.i18n.Tr "repo.settings.add_webhook"}} + {{.i18n.Tr "repo.settings.hooks"}} +
+
    +
  • {{.i18n.Tr "org.settings.hooks_desc" | Str2html}}
  • + {{range .Webhooks}} +
  • + {{if .IsActive}} + + {{else}} + + {{end}} + {{.Url}} + + +
  • + {{end}} +
+
+
+
+
+
+
+ +{{template "ng/base/footer" .}} diff --git a/templates/org/settings/nav.tmpl b/templates/org/settings/nav.tmpl index 950569d6..954893c6 100644 --- a/templates/org/settings/nav.tmpl +++ b/templates/org/settings/nav.tmpl @@ -5,7 +5,8 @@ - \ No newline at end of file + diff --git a/templates/repo/settings/hook_gogs.tmpl b/templates/repo/settings/hook_gogs.tmpl index 35b58995..31a04ce0 100644 --- a/templates/repo/settings/hook_gogs.tmpl +++ b/templates/repo/settings/hook_gogs.tmpl @@ -1,5 +1,5 @@
-
+ {{.CsrfTokenHtml}}
{{.i18n.Tr "repo.settings.add_webhook_desc" | Str2html}}
diff --git a/templates/repo/settings/hook_slack.tmpl b/templates/repo/settings/hook_slack.tmpl index 50d28e2f..ed7a42e6 100644 --- a/templates/repo/settings/hook_slack.tmpl +++ b/templates/repo/settings/hook_slack.tmpl @@ -1,5 +1,5 @@
- + {{.CsrfTokenHtml}}
{{.i18n.Tr "repo.settings.add_slack_hook_desc" | Str2html}}
-- cgit v1.2.3 From 0ddb1eb769610f1d74997ad12d848ae5a0bdb4ab Mon Sep 17 00:00:00 2001 From: Unknwon Date: Sun, 7 Sep 2014 19:02:58 -0400 Subject: Finish new install page, almost ready for 0.5 release --- conf/locale/locale_en-US.ini | 47 +++++- conf/locale/locale_zh-CN.ini | 45 ++++++ gogs.go | 2 +- modules/auth/user_form.go | 21 +-- public/ng/css/gogs.css | 13 ++ public/ng/js/gogs.js | 42 +++++- public/ng/js/min/gogs-min.js | 10 +- public/ng/less/gogs/external.less | 170 +++++++++++---------- routers/install.go | 49 +++--- templates/.VERSION | 2 +- templates/install.tmpl | 306 ++++++++++++++++---------------------- 11 files changed, 408 insertions(+), 299 deletions(-) (limited to 'public/ng/js/gogs.js') diff --git a/conf/locale/locale_en-US.ini b/conf/locale/locale_en-US.ini index e8329933..50fbc3cd 100644 --- a/conf/locale/locale_en-US.ini +++ b/conf/locale/locale_en-US.ini @@ -38,6 +38,50 @@ issues = Issues cancel = Cancel +[install] +install = Installation +title = Install Steps For First-time Run +requite_db_desc = Gogs requires MySQL, PostgreSQL or SQLite3, but SQLite3 is usually available in the official binary version. +db_type = Database Type +host = Host +user = User +password = Password +db_name = Database Name +db_helper = Please use INNODB engine with utf8_general_ci charset for MySQL. +ssl_mode = SSL Mode +path = Path +sqlite_helper = The file path of SQLite3 database. +general_title = General Settings of Gogs +repo_path = Repository Root Path +repo_path_helper = All Git remote repositories will be saved to this directory. +run_user = Run User +run_user_helper = The user must have access to Repository Root Path and run Gogs. +domain = Domain +domain_helper = This affects SSH clone URLs. +app_url = Application URL +app_url_helper = This affects HTTP/HTTPS clone URL and somewhere in e-mail. +email_title = Email Service Settings(Optional) +smtp_host = SMTP Host +mailer_user = Sender E-mail +mailer_password = Sender Password +notify_title = Notification Settings(Optional) +register_confirm = Enable Register Confirmation +mail_notify = Enable Mail Notification +admin_title = Admin Account Settings +admin_name = Username +admin_password = Password +confirm_password = Confirm Password +admin_email = E-mail +install_gogs = Install Gogs +test_git_failed = Fail to test 'git' command: %v +sqlite3_not_available = Your release version does not support SQLite3, please download the official binary version from http://gogs.io/docs/installation/install_from_binary.html, NOT the gobuild version. +invalid_db_setting = Database setting is not correct: %v +invalid_repo_path = Repository root path is invalid: %v +run_user_not_match = Run user isn't the current user: %s -> %s +save_config_failed = Fail to save configuration: %v +invalid_admin_setting = Admin account setting is invalid: %v +install_success = Welcome! We're glad that you choose Gogs, have fun and take care. + [home] uname_holder = Username or E-mail password_holder = Password @@ -84,6 +128,7 @@ HttpsUrl = HTTPS URL PayloadUrl = Payload URL TeamName = Team name AuthName = Authorization name +AdminEmail = Admin E-mail require_error = ` cannot be empty.` alpha_dash_error = ` must be valid alpha or numeric or dash(-_) characters.` @@ -94,7 +139,7 @@ email_error = ` is not a valid e-mail address.` url_error = ` is not a valid URL.` unknown_error = Unknown error: captcha_incorrect = Captcha didn't match. -password_not_match = Password and re-type password are not same. +password_not_match = Password and confirm password are not same. username_been_taken = Username has been already taken. repo_name_been_taken = Repository name has been already taken. diff --git a/conf/locale/locale_zh-CN.ini b/conf/locale/locale_zh-CN.ini index a61a54ce..7d10142e 100644 --- a/conf/locale/locale_zh-CN.ini +++ b/conf/locale/locale_zh-CN.ini @@ -38,6 +38,50 @@ issues = 工单管理 cancel = 取消 +[install] +install = 安装页面 +title = 首次运行安装程序 +requite_db_desc = Gogs 允许后端数据库为 MySQL、PostgreSQL 或 SQLite3,但是 SQLite3 一般只有官方二进制发行版才支持。 +db_type = 数据库类型 +host = 数据库主机 +user = 数据库用户 +password = 数据库用户密码 +db_name = 数据库名称 +db_helper = 如果您使用 MySQL,请使用 INNODB 引擎以及 utf8_general_ci 字符集。 +ssl_mode = SSL 模式 +path = 数据库文件路径 +sqlite_helper = SQLite3 数据库的文件路径。 +general_title = 应用基本设置 +repo_path = 仓库根目录 +repo_path_helper = 所有 Git 远程仓库都将被存放于该目录。 +run_user = 运行系统用户 +run_user_helper = 该用户必须具有对仓库根目录和运行 Gogs 的操作权限。 +domain = 域名 +domain_helper = 该设置影响 SSH 克隆地址。 +app_url = 应用 URL +app_url_helper = 该设置影响 HTTP/HTTPS 克隆地址和一些邮箱中的链接。 +email_title = 邮件服务设置(可选) +smtp_host = SMTP 主机 +mailer_user = 发送邮箱 +mailer_password = 发送邮箱密码 +notify_title = 通知提醒设置(可选) +register_confirm = 启用注册邮箱确认 +mail_notify = 启用邮件通知提醒 +admin_title = 管理员帐号设置 +admin_name = 管理员用户名 +admin_password = 管理员密码 +confirm_password = 确认密码 +admin_email = 管理员邮箱 +install_gogs = 立即安装 +test_git_failed = 无法识别 'git' 命令:%v +sqlite3_not_available = 您所使用的发行版不支持 SQLite3,请从 http://gogs.io/docs/installation/install_from_binary.html 下载官方二进制发行版,而不是 gobuild 版本。 +invalid_db_setting = 数据库设置不正确:%v +invalid_repo_path = 仓库根目录设置不正确:%v +run_user_not_match = 运行系统用户非当前用户:%s -> %s +save_config_failed = 应用配置保存失败:%v +invalid_admin_setting = 管理员帐户设置不正确:%v +install_success = 您好!我们很高兴您选择使用 Gogs,祝您使用愉快,代码从此无 BUG! + [home] uname_holder = 用户名或邮箱 password_holder = 密码 @@ -84,6 +128,7 @@ HttpsUrl = HTTPS URL 地址 PayloadUrl = 推送地址 TeamName = 团队名称 AuthName = 认证名称 +AdminEmail = 管理员邮箱 require_error = 不能为空。 alpha_dash_error = 必须为英文字母、阿拉伯数字或横线(-_)。 diff --git a/gogs.go b/gogs.go index 1c012d40..2ca0f9ad 100644 --- a/gogs.go +++ b/gogs.go @@ -17,7 +17,7 @@ import ( "github.com/gogits/gogs/modules/setting" ) -const APP_VER = "0.4.9.0905 Beta" +const APP_VER = "0.5.0.0907 Beta" func init() { runtime.GOMAXPROCS(runtime.NumCPU()) diff --git a/modules/auth/user_form.go b/modules/auth/user_form.go index 51a07b91..93bd01a9 100644 --- a/modules/auth/user_form.go +++ b/modules/auth/user_form.go @@ -13,24 +13,25 @@ import ( type InstallForm struct { Database string `form:"database" binding:"Required"` - Host string `form:"host"` - User string `form:"user"` - Passwd string `form:"passwd"` + DbHost string `form:"host"` + DbUser string `form:"user"` + DbPasswd string `form:"passwd"` DatabaseName string `form:"database_name"` SslMode string `form:"ssl_mode"` DatabasePath string `form:"database_path"` - RepoRootPath string `form:"repo_path"` - RunUser string `form:"run_user"` - Domain string `form:"domain"` - AppUrl string `form:"app_url"` - AdminName string `form:"admin_name" binding:"Required;AlphaDashDot;MaxSize(30)"` - AdminPasswd string `form:"admin_pwd" binding:"Required;MinSize(6);MaxSize(255)"` - AdminEmail string `form:"admin_email" binding:"Required;Email;MaxSize(50)"` + RepoRootPath string `form:"repo_path" binding:"Required"` + RunUser string `form:"run_user" binding:"Required"` + Domain string `form:"domain" binding:"Required"` + AppUrl string `form:"app_url" binding:"Required"` SmtpHost string `form:"smtp_host"` SmtpEmail string `form:"mailer_user"` SmtpPasswd string `form:"mailer_pwd"` RegisterConfirm string `form:"register_confirm"` MailNotify string `form:"mail_notify"` + AdminName string `form:"admin_name" binding:"Required;AlphaDashDot;MaxSize(30)"` + AdminPasswd string `form:"admin_pwd" binding:"Required;MinSize(6);MaxSize(255)"` + ConfirmPasswd string `form:"confirm_passwd" binding:"Required;MinSize(6);MaxSize(255)"` + AdminEmail string `form:"admin_email" binding:"Required;Email;MaxSize(50)"` } func (f *InstallForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) { diff --git a/public/ng/css/gogs.css b/public/ng/css/gogs.css index 636a8667..f4d33540 100644 --- a/public/ng/css/gogs.css +++ b/public/ng/css/gogs.css @@ -669,6 +669,19 @@ ol.linenums { #feature-wrapper .grid-1-2 { margin-bottom: 30px; } +#install-form { + padding: 15px; +} +#install-form label { + width: 35%; +} +#install-form input { + width: 30%; +} +#install-form input[type="checkbox"], +#install-form input[type="radio"] { + width: auto; +} /* The dashboard page style */ diff --git a/public/ng/js/gogs.js b/public/ng/js/gogs.js index c08a887a..cb23c8f9 100644 --- a/public/ng/js/gogs.js +++ b/public/ng/js/gogs.js @@ -521,7 +521,7 @@ function initAdmin() { e.preventDefault(); return true; } - var $form = $('user-profile-form'); + var $form = $('#user-profile-form'); $form.attr('action', $form.data('delete-url')); }); // Create authorization. @@ -547,6 +547,43 @@ function initAdmin() { }); } +function initInstall() { + // Change database type. + (function () { + var mysql_default = '127.0.0.1:3306'; + var postgres_default = '127.0.0.1:5432'; + + $('#install-database').on("change", function () { + var val = $(this).val(); + if (val != "SQLite3") { + $('.server-sql').show(); + $('.sqlite-setting').addClass("hide"); + if (val == "PostgreSQL") { + $('.pgsql-setting').removeClass("hide"); + + // Change the host value to the Postgres default, but only + // if the user hasn't already changed it from the MySQL + // default. + if ($('#database-host').val() == mysql_default) { + $('#database-host').val(postgres_default); + } + } else if (val == 'MySQL') { + $('.pgsql-setting').addClass("hide"); + if ($('#database-host').val() == postgres_default) { + $('#database-host').val(mysql_default); + } + } else { + $('.pgsql-setting').addClass("hide"); + } + } else { + $('.server-sql').hide(); + $('.pgsql-setting').hide(); + $('.sqlite-setting').removeClass("hide"); + } + }); + }()); +} + $(document).ready(function () { initCore(); if ($('#user-profile-setting').length) { @@ -579,6 +616,9 @@ $(document).ready(function () { if ($('#admin-setting').length) { initAdmin(); } + if ($('#install-form').length) { + initInstall(); + } Tabs('#dashboard-sidebar-menu'); diff --git a/public/ng/js/min/gogs-min.js b/public/ng/js/min/gogs-min.js index 4e4d63fe..8151792f 100644 --- a/public/ng/js/min/gogs-min.js +++ b/public/ng/js/min/gogs-min.js @@ -1,5 +1,5 @@ -function Tabs(e){function t(e){console.log("hide",e),e.removeClass("js-tab-nav-show"),$(e.data("tab-target")).removeClass("js-tab-show").hide()}function n(e){console.log("show",e),e.addClass("js-tab-nav-show"),$(e.data("tab-target")).addClass("js-tab-show").show()}var r=$(e);if(r.length){var i=r.find(".js-tab-nav-show");i.length&&$(i.data("tab-target")).addClass("js-tab-show"),r.on("click",".js-tab-nav",function(){var e=$(this);e.hasClass("js-tab-nav-show")||(i=r.find(".js-tab-nav-show").eq(0),t(i),n(e))}),console.log("init tabs @",e)}}function initCore(){Gogs.renderMarkdown(),Gogs.renderCodeView()}function homepage(){$("#promo-form").submit(function(e){return""===$("#username").val()?(e.preventDefault(),window.location.href="/user/login",!0):void 0}),$("#register-button").click(function(e){return""===$("#username").val()?(e.preventDefault(),window.location.href="/user/sign_up",!0):void $("#promo-form").attr("action","/user/sign_up")})}function settingsProfile(){$("#user-profile-form").submit(function(e){return $("#username").data("uname")==$("#username").val()||confirm("Username has been changed, do you want to continue?")?void 0:(e.preventDefault(),!0)})}function settingsSSHKeys(){$("#ssh-add").click(function(){$("#user-ssh-add-form").removeClass("hide")})}function settingsDelete(){$("#delete-account-button").click(function(e){return confirm("This account is going to deleted, do you want to continue?")?void 0:(e.preventDefault(),!0)})}!function(e,t){"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){function n(e){var t=e.length,n=ot.type(e);return"function"===n||ot.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||0===t||"number"==typeof t&&t>0&&t-1 in e}function r(e,t,n){if(ot.isFunction(t))return ot.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return ot.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(pt.test(t))return ot.filter(t,e,n);t=ot.filter(t,e)}return ot.grep(e,function(e){return ot.inArray(e,t)>=0!==n})}function i(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}function o(e){var t=wt[e]={};return ot.each(e.match(xt)||[],function(e,n){t[n]=!0}),t}function a(){mt.addEventListener?(mt.removeEventListener("DOMContentLoaded",s,!1),e.removeEventListener("load",s,!1)):(mt.detachEvent("onreadystatechange",s),e.detachEvent("onload",s))}function s(){(mt.addEventListener||"load"===event.type||"complete"===mt.readyState)&&(a(),ot.ready())}function l(e,t,n){if(void 0===n&&1===e.nodeType){var r="data-"+t.replace(kt,"-$1").toLowerCase();if(n=e.getAttribute(r),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:Et.test(n)?ot.parseJSON(n):n}catch(i){}ot.data(e,t,n)}else n=void 0}return n}function u(e){var t;for(t in e)if(("data"!==t||!ot.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}function c(e,t,n,r){if(ot.acceptData(e)){var i,o,a=ot.expando,s=e.nodeType,l=s?ot.cache:e,u=s?e[a]:e[a]&&a;if(u&&l[u]&&(r||l[u].data)||void 0!==n||"string"!=typeof t)return u||(u=s?e[a]=G.pop()||ot.guid++:a),l[u]||(l[u]=s?{}:{toJSON:ot.noop}),("object"==typeof t||"function"==typeof t)&&(r?l[u]=ot.extend(l[u],t):l[u].data=ot.extend(l[u].data,t)),o=l[u],r||(o.data||(o.data={}),o=o.data),void 0!==n&&(o[ot.camelCase(t)]=n),"string"==typeof t?(i=o[t],null==i&&(i=o[ot.camelCase(t)])):i=o,i}}function d(e,t,n){if(ot.acceptData(e)){var r,i,o=e.nodeType,a=o?ot.cache:e,s=o?e[ot.expando]:ot.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){ot.isArray(t)?t=t.concat(ot.map(t,ot.camelCase)):t in r?t=[t]:(t=ot.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;for(;i--;)delete r[t[i]];if(n?!u(r):!ot.isEmptyObject(r))return}(n||(delete a[s].data,u(a[s])))&&(o?ot.cleanData([e],!0):rt.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}function f(){return!0}function p(){return!1}function h(){try{return mt.activeElement}catch(e){}}function m(e){var t=Ht.split("|"),n=e.createDocumentFragment();if(n.createElement)for(;t.length;)n.createElement(t.pop());return n}function g(e,t){var n,r,i=0,o=typeof e.getElementsByTagName!==St?e.getElementsByTagName(t||"*"):typeof e.querySelectorAll!==St?e.querySelectorAll(t||"*"):void 0;if(!o)for(o=[],n=e.childNodes||e;null!=(r=n[i]);i++)!t||ot.nodeName(r,t)?o.push(r):ot.merge(o,g(r,t));return void 0===t||t&&ot.nodeName(e,t)?ot.merge([e],o):o}function v(e){jt.test(e.type)&&(e.defaultChecked=e.checked)}function y(e,t){return ot.nodeName(e,"table")&&ot.nodeName(11!==t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function b(e){return e.type=(null!==ot.find.attr(e,"type"))+"/"+e.type,e}function x(e){var t=Zt.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function w(e,t){for(var n,r=0;null!=(n=e[r]);r++)ot._data(n,"globalEval",!t||ot._data(t[r],"globalEval"))}function C(e,t){if(1===t.nodeType&&ot.hasData(e)){var n,r,i,o=ot._data(e),a=ot._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)ot.event.add(t,n,s[n][r])}a.data&&(a.data=ot.extend({},a.data))}}function S(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!rt.noCloneEvent&&t[ot.expando]){i=ot._data(t);for(r in i.events)ot.removeEvent(t,r,i.handle);t.removeAttribute(ot.expando)}"script"===n&&t.text!==e.text?(b(t).text=e.text,x(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),rt.html5Clone&&e.innerHTML&&!ot.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&jt.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}function T(t,n){var r,i=ot(n.createElement(t)).appendTo(n.body),o=e.getDefaultComputedStyle&&(r=e.getDefaultComputedStyle(i[0]))?r.display:ot.css(i[0],"display");return i.detach(),o}function E(e){var t=mt,n=Jt[e];return n||(n=T(e,t),"none"!==n&&n||(Kt=(Kt||ot("
',srcAction:"iframe_src",patterns:{youtube:{index:"youtube.com",id:"v=",src:"//www.youtube.com/embed/%id%?autoplay=1"},vimeo:{index:"vimeo.com/",id:"/",src:"//player.vimeo.com/video/%id%?autoplay=1"},gmaps:{index:"//maps.google.",src:"%id%&output=embed"}}},proto:{initIframe:function(){t.types.push(Z),x("BeforeChange",function(e,t,n){t!==n&&(t===Z?D():n===Z&&D(!0))}),x(l+"."+Z,function(){D()})},getIframe:function(n,i){var o=n.src,r=t.st.iframe;e.each(r.patterns,function(){return o.indexOf(this.index)>-1?(this.id&&(o="string"==typeof this.id?o.substr(o.lastIndexOf(this.id)+this.id.length,o.length):this.id.call(this,o)),o=this.src.replace("%id%",o),!1):void 0});var a={};return r.srcAction&&(a[r.srcAction]=o),t._parseMarkup(i,a,n),t.updateStatus("ready"),i}}});var K=function(e){var n=t.items.length;return e>n-1?e-n:0>e?n+e:e},Y=function(e,t,n){return e.replace(/%curr%/gi,t+1).replace(/%total%/gi,n)};e.magnificPopup.registerModule("gallery",{options:{enabled:!1,arrowMarkup:'',preload:[0,2],navigateByImgClick:!0,arrows:!0,tPrev:"Previous (Left arrow key)",tNext:"Next (Right arrow key)",tCounter:"%curr% of %total%"},proto:{initGallery:function(){var n=t.st.gallery,i=".mfp-gallery",r=Boolean(e.fn.mfpFastClick);return t.direction=!0,n&&n.enabled?(a+=" mfp-gallery",x(f+i,function(){n.navigateByImgClick&&t.wrap.on("click"+i,".mfp-img",function(){return t.items.length>1?(t.next(),!1):void 0}),o.on("keydown"+i,function(e){37===e.keyCode?t.prev():39===e.keyCode&&t.next()})}),x("UpdateStatus"+i,function(e,n){n.text&&(n.text=Y(n.text,t.currItem.index,t.items.length))}),x(p+i,function(e,i,o,r){var a=t.items.length;o.counter=a>1?Y(n.tCounter,r.index,a):""}),x("BuildControls"+i,function(){if(t.items.length>1&&n.arrows&&!t.arrowLeft){var i=n.arrowMarkup,o=t.arrowLeft=e(i.replace(/%title%/gi,n.tPrev).replace(/%dir%/gi,"left")).addClass(y),a=t.arrowRight=e(i.replace(/%title%/gi,n.tNext).replace(/%dir%/gi,"right")).addClass(y),s=r?"mfpFastClick":"click";o[s](function(){t.prev()}),a[s](function(){t.next()}),t.isIE7&&(k("b",o[0],!1,!0),k("a",o[0],!1,!0),k("b",a[0],!1,!0),k("a",a[0],!1,!0)),t.container.append(o.add(a))}}),x(m+i,function(){t._preloadTimeout&&clearTimeout(t._preloadTimeout),t._preloadTimeout=setTimeout(function(){t.preloadNearbyImages(),t._preloadTimeout=null},16)}),x(l+i,function(){o.off(i),t.wrap.off("click"+i),t.arrowLeft&&r&&t.arrowLeft.add(t.arrowRight).destroyMfpFastClick(),t.arrowRight=t.arrowLeft=null}),void 0):!1},next:function(){t.direction=!0,t.index=K(t.index+1),t.updateItemHTML()},prev:function(){t.direction=!1,t.index=K(t.index-1),t.updateItemHTML()},goTo:function(e){t.direction=e>=t.index,t.index=e,t.updateItemHTML()},preloadNearbyImages:function(){var e,n=t.st.gallery.preload,i=Math.min(n[0],t.items.length),o=Math.min(n[1],t.items.length);for(e=1;(t.direction?o:i)>=e;e++)t._preloadItem(t.index+e);for(e=1;(t.direction?i:o)>=e;e++)t._preloadItem(t.index-e)},_preloadItem:function(n){if(n=K(n),!t.items[n].preloaded){var i=t.items[n];i.parsed||(i=t.parseEl(n)),T("LazyLoad",i),"image"===i.type&&(i.img=e('').on("load.mfploader",function(){i.hasSize=!0}).on("error.mfploader",function(){i.hasSize=!0,i.loadError=!0,T("LazyLoadError",i)}).attr("src",i.src)),i.preloaded=!0}}}});var U="retina";e.magnificPopup.registerModule(U,{options:{replaceSrc:function(e){return e.src.replace(/\.\w+$/,function(e){return"@2x"+e})},ratio:1},proto:{initRetina:function(){if(window.devicePixelRatio>1){var e=t.st.retina,n=e.ratio;n=isNaN(n)?n():n,n>1&&(x("ImageHasSize."+U,function(e,t){t.img.css({"max-width":t.img[0].naturalWidth/n,width:"100%"})}),x("ElementParse."+U,function(t,i){i.src=e.replaceSrc(i,n)}))}}}}),function(){var t=1e3,n="ontouchstart"in window,i=function(){I.off("touchmove"+r+" touchend"+r)},o="mfpFastClick",r="."+o;e.fn.mfpFastClick=function(o){return e(this).each(function(){var a,s=e(this);if(n){var l,c,d,u,p,f;s.on("touchstart"+r,function(e){u=!1,f=1,p=e.originalEvent?e.originalEvent.touches[0]:e.touches[0],c=p.clientX,d=p.clientY,I.on("touchmove"+r,function(e){p=e.originalEvent?e.originalEvent.touches:e.touches,f=p.length,p=p[0],(Math.abs(p.clientX-c)>10||Math.abs(p.clientY-d)>10)&&(u=!0,i())}).on("touchend"+r,function(e){i(),u||f>1||(a=!0,e.preventDefault(),clearTimeout(l),l=setTimeout(function(){a=!1},t),o())})})}s.on("click"+r,function(){a||o()})})},e.fn.destroyMfpFastClick=function(){e(this).off("touchstart"+r+" click"+r),n&&I.off("touchmove"+r+" touchend"+r)}}(),_()})(window.jQuery||window.Zepto); \ No newline at end of file diff --git a/public/ng/js/min/gogs-min.js b/public/ng/js/min/gogs-min.js index 1a805b5f..d27e0a8b 100644 --- a/public/ng/js/min/gogs-min.js +++ b/public/ng/js/min/gogs-min.js @@ -1,5 +1,5 @@ -function Tabs(e){function t(e){console.log("hide",e),e.removeClass("js-tab-nav-show"),$(e.data("tab-target")).removeClass("js-tab-show").hide()}function n(e){console.log("show",e),e.addClass("js-tab-nav-show"),$(e.data("tab-target")).addClass("js-tab-show").show()}var r=$(e);if(r.length){var i=r.find(".js-tab-nav-show");i.length&&$(i.data("tab-target")).addClass("js-tab-show"),r.on("click",".js-tab-nav",function(e){e.preventDefault();var o=$(this);o.hasClass("js-tab-nav-show")||(i=r.find(".js-tab-nav-show").eq(0),t(i),n(o))}),console.log("init tabs @",e)}}function Preview(e,t){function n(e){return e.find(".js-preview-input").eq(0)}function r(e){return e.hasClass("js-preview-container")?e:e.find(".js-preview-container").eq(0)}var i=$(e),o=$(t),a=n(o);if(!a.length)return void console.log("[preview]: no preview input");var s=r(o);return s.length?(i.on("click",function(){$.post("/api/v1/markdown",{text:a.val()},function(e){s.html(e)})}),void console.log("[preview]: init preview @",e,"&",t)):void console.log("[preview]: no preview container")}function initCore(){Gogs.renderMarkdown(),Gogs.renderCodeView(),$(".js-tab-nav").click(function(e){$(this).hasClass("js-tab-nav-show")||($(this).parent().find(".js-tab-nav-show").each(function(){$(this).removeClass("js-tab-nav-show"),$($(this).data("tab-target")).hide()}),$(this).addClass("js-tab-nav-show"),$($(this).data("tab-target")).show()),e.preventDefault()})}function initUserSetting(){$("#user-profile-form").submit(function(e){var t=$("#username");return t.data("uname")==t.val()||confirm("Username has been changed, do you want to continue?")?void 0:(e.preventDefault(),!0)}),$("#ssh-add").click(function(){$("#user-ssh-add-form").removeClass("hide")}),$("#delete-account-button").click(function(e){return confirm("This account is going to be deleted, do you want to continue?")?void 0:(e.preventDefault(),!0)})}function initRepoCreate(){$("#repo-create-owner-list").on("click","li",function(){if(!$(this).hasClass("checked")){var e=$(this).data("uid");$("#repo-owner-id").val(e),$("#repo-owner-avatar").attr("src",$(this).find("img").attr("src")),$("#repo-owner-name").text($(this).text().trim()),$(this).parent().find(".checked").removeClass("checked"),$(this).addClass("checked"),console.log("set repo owner to uid :",e,$(this).text().trim())}}),$("#auth-button").click(function(e){$("#repo-migrate-auth").slideToggle("fast"),e.preventDefault()}),console.log("initRepoCreate")}function initRepo(){$("#repo-clone-ssh").click(function(){$(this).removeClass("btn-gray").addClass("btn-blue"),$("#repo-clone-https").removeClass("btn-blue").addClass("btn-gray"),$("#repo-clone-url").val($(this).data("link")),$(".clone-url").text($(this).data("link"))}),$("#repo-clone-https").click(function(){$(this).removeClass("btn-gray").addClass("btn-blue"),$("#repo-clone-ssh").removeClass("btn-blue").addClass("btn-gray"),$("#repo-clone-url").val($(this).data("link")),$(".clone-url").text($(this).data("link"))});var e=$("#repo-clone-copy");e.hover(function(){Gogs.bindCopy($(this))}),e.tipsy({fade:!0})}function initHookTypeChange(){$("select#hook-type").on("change",function(){hookTypes=["Gogs","Slack"];var e=$(this).val();hookTypes.forEach(function(t){e===t?$("div#"+t.toLowerCase()).toggleShow():$("div#"+t.toLowerCase()).toggleHide()})})}function initRepoSetting(){$("#repo-setting-form").submit(function(e){var t=$("#repo_name");return t.data("repo-name")==t.val()||confirm("Repository name has been changed, do you want to continue?")?void 0:(e.preventDefault(),!0)}),initHookTypeChange(),$("#transfer-button").click(function(){$("#transfer-form").show()}),$("#delete-button").click(function(){$("#delete-form").show()}),$("#repo-collab-list hr:last-child").remove();var e=$("#repo-collaborator").next().next().find("ul");$("#repo-collaborator").on("keyup",function(){var t=$(this);return t.val()?void Gogs.searchUsers(t.val(),e):void e.toggleHide()}).on("focus",function(){$(this).val()?e.toggleShow():e.toggleHide()}).next().next().find("ul").on("click","li",function(){$("#repo-collaborator").val($(this).text()),e.toggleHide()})}function initOrgSetting(){$("#org-setting-form").submit(function(e){var t=$("#orgname");return t.data("orgname")==t.val()||confirm("Organization name has been changed, do you want to continue?")?void 0:(e.preventDefault(),!0)}),$("#delete-org-button").click(function(e){return confirm("This organization is going to be deleted, do you want to continue?")?void 0:(e.preventDefault(),!0)}),initHookTypeChange()}function initInvite(){var e=$("#org-member-invite-list");$("#org-member-invite").on("keyup",function(){var t=$(this);return t.val()?void Gogs.searchUsers(t.val(),e):void e.toggleHide()}).on("focus",function(){$(this).val()?e.toggleShow():e.toggleHide()}).next().next().find("ul").on("click","li",function(){$("#org-member-invite").val($(this).text()),e.toggleHide()})}function initOrgTeamCreate(){$("#org-team-delete").click(function(e){if(!confirm("This team is going to be deleted, do you want to continue?"))return e.preventDefault(),!0;var t=$("#team-create-form");t.attr("action",t.data("delete-url"))})}function initTeamMembersList(){var e=$("#org-team-members-list");$("#org-team-members-add").on("keyup",function(){var t=$(this);return t.val()?void Gogs.searchUsers(t.val(),e):void e.toggleHide()}).on("focus",function(){$(this).val()?e.toggleShow():e.toggleHide()}).next().next().find("ul").on("click","li",function(){$("#org-team-members-add").val($(this).text()),e.toggleHide()})}function initTeamRepositoriesList(){var e=$("#org-team-repositories-list");$("#org-team-repositories-add").on("keyup",function(){var t=$(this);return t.val()?void Gogs.searchRepos(t.val(),e,"uid="+t.data("uid")):void e.toggleHide()}).on("focus",function(){$(this).val()?e.toggleShow():e.toggleHide()}).next().next().find("ul").on("click","li",function(){$("#org-team-repositories-add").val($(this).text()),e.toggleHide()})}function initAdmin(){$("#login-type").on("change",function(){var e=$(this).val();e.indexOf("0-")+1?($(".auth-name").toggleHide(),$(".pwd").find("input").attr("required","required").end().toggleShow()):($(".pwd").find("input").removeAttr("required").end().toggleHide(),$(".auth-name").toggleShow())}),$("#user-delete").click(function(e){if(!confirm("This account is going to be deleted, do you want to continue?"))return e.preventDefault(),!0;var t=$("#user-profile-form");t.attr("action",t.data("delete-url"))}),$("#auth-type").on("change",function(){var e=$(this).val();2==e&&($(".ldap").toggleShow(),$(".smtp").toggleHide()),3==e&&($(".smtp").toggleShow(),$(".ldap").toggleHide())}),$("#auth-delete").click(function(e){if(!confirm("This authorization is going to be deleted, do you want to continue?"))return e.preventDefault(),!0;var t=$("auth-setting-form");t.attr("action",t.data("delete-url"))})}function initInstall(){!function(){var e="127.0.0.1:3306",t="127.0.0.1:5432";$("#install-database").on("change",function(){var n=$(this).val();"SQLite3"!=n?($(".server-sql").show(),$(".sqlite-setting").addClass("hide"),"PostgreSQL"==n?($(".pgsql-setting").removeClass("hide"),$("#database-host").val()==e&&$("#database-host").val(t)):"MySQL"==n?($(".pgsql-setting").addClass("hide"),$("#database-host").val()==t&&$("#database-host").val(e)):$(".pgsql-setting").addClass("hide")):($(".server-sql").hide(),$(".pgsql-setting").hide(),$(".sqlite-setting").removeClass("hide"))})}()}function initProfile(){$("#profile-avatar").tipsy({fade:!0})}function homepage(){$("#promo-form").submit(function(e){return""===$("#username").val()?(e.preventDefault(),window.location.href=Gogs.AppSubUrl+"/user/login",!0):void 0}),$("#register-button").click(function(e){return""===$("#username").val()?(e.preventDefault(),window.location.href=Gogs.AppSubUrl+"/user/sign_up",!0):void $("#promo-form").attr("action",Gogs.AppSubUrl+"/user/sign_up")})}!function(e,t){"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){function n(e){var t=e.length,n=ot.type(e);return"function"===n||ot.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||0===t||"number"==typeof t&&t>0&&t-1 in e}function r(e,t,n){if(ot.isFunction(t))return ot.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return ot.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(pt.test(t))return ot.filter(t,e,n);t=ot.filter(t,e)}return ot.grep(e,function(e){return ot.inArray(e,t)>=0!==n})}function i(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}function o(e){var t=wt[e]={};return ot.each(e.match(xt)||[],function(e,n){t[n]=!0}),t}function a(){mt.addEventListener?(mt.removeEventListener("DOMContentLoaded",s,!1),e.removeEventListener("load",s,!1)):(mt.detachEvent("onreadystatechange",s),e.detachEvent("onload",s))}function s(){(mt.addEventListener||"load"===event.type||"complete"===mt.readyState)&&(a(),ot.ready())}function l(e,t,n){if(void 0===n&&1===e.nodeType){var r="data-"+t.replace(Et,"-$1").toLowerCase();if(n=e.getAttribute(r),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:kt.test(n)?ot.parseJSON(n):n}catch(i){}ot.data(e,t,n)}else n=void 0}return n}function u(e){var t;for(t in e)if(("data"!==t||!ot.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}function c(e,t,n,r){if(ot.acceptData(e)){var i,o,a=ot.expando,s=e.nodeType,l=s?ot.cache:e,u=s?e[a]:e[a]&&a;if(u&&l[u]&&(r||l[u].data)||void 0!==n||"string"!=typeof t)return u||(u=s?e[a]=V.pop()||ot.guid++:a),l[u]||(l[u]=s?{}:{toJSON:ot.noop}),("object"==typeof t||"function"==typeof t)&&(r?l[u]=ot.extend(l[u],t):l[u].data=ot.extend(l[u].data,t)),o=l[u],r||(o.data||(o.data={}),o=o.data),void 0!==n&&(o[ot.camelCase(t)]=n),"string"==typeof t?(i=o[t],null==i&&(i=o[ot.camelCase(t)])):i=o,i}}function d(e,t,n){if(ot.acceptData(e)){var r,i,o=e.nodeType,a=o?ot.cache:e,s=o?e[ot.expando]:ot.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){ot.isArray(t)?t=t.concat(ot.map(t,ot.camelCase)):t in r?t=[t]:(t=ot.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;for(;i--;)delete r[t[i]];if(n?!u(r):!ot.isEmptyObject(r))return}(n||(delete a[s].data,u(a[s])))&&(o?ot.cleanData([e],!0):rt.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}function f(){return!0}function p(){return!1}function h(){try{return mt.activeElement}catch(e){}}function m(e){var t=Pt.split("|"),n=e.createDocumentFragment();if(n.createElement)for(;t.length;)n.createElement(t.pop());return n}function g(e,t){var n,r,i=0,o=typeof e.getElementsByTagName!==St?e.getElementsByTagName(t||"*"):typeof e.querySelectorAll!==St?e.querySelectorAll(t||"*"):void 0;if(!o)for(o=[],n=e.childNodes||e;null!=(r=n[i]);i++)!t||ot.nodeName(r,t)?o.push(r):ot.merge(o,g(r,t));return void 0===t||t&&ot.nodeName(e,t)?ot.merge([e],o):o}function v(e){jt.test(e.type)&&(e.defaultChecked=e.checked)}function y(e,t){return ot.nodeName(e,"table")&&ot.nodeName(11!==t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function b(e){return e.type=(null!==ot.find.attr(e,"type"))+"/"+e.type,e}function x(e){var t=Zt.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function w(e,t){for(var n,r=0;null!=(n=e[r]);r++)ot._data(n,"globalEval",!t||ot._data(t[r],"globalEval"))}function C(e,t){if(1===t.nodeType&&ot.hasData(e)){var n,r,i,o=ot._data(e),a=ot._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)ot.event.add(t,n,s[n][r])}a.data&&(a.data=ot.extend({},a.data))}}function S(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!rt.noCloneEvent&&t[ot.expando]){i=ot._data(t);for(r in i.events)ot.removeEvent(t,r,i.handle);t.removeAttribute(ot.expando)}"script"===n&&t.text!==e.text?(b(t).text=e.text,x(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),rt.html5Clone&&e.innerHTML&&!ot.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&jt.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}function T(t,n){var r,i=ot(n.createElement(t)).appendTo(n.body),o=e.getDefaultComputedStyle&&(r=e.getDefaultComputedStyle(i[0]))?r.display:ot.css(i[0],"display");return i.detach(),o}function k(e){var t=mt,n=Jt[e];return n||(n=T(e,t),"none"!==n&&n||(Kt=(Kt||ot("