From 8c9338a5377c60c84cdee1f5781b3de5933bb3b0 Mon Sep 17 00:00:00 2001 From: Unknwon Date: Wed, 12 Nov 2014 06:48:50 -0500 Subject: [PATCH] add personal access token panel #12 --- cmd/web.go | 6 +- conf/locale/locale_en-US.ini | 11 ++++ gogs.go | 2 +- models/models.go | 3 +- models/publickey.go | 6 +- models/token.go | 69 +++++++++++++++++++++ modules/auth/auth.go | 28 +++++++-- modules/auth/publickey_form.go | 19 ------ modules/auth/user_form.go | 17 +++++ modules/base/tool.go | 7 +++ modules/middleware/context.go | 2 +- public/ng/css/gogs.css | 39 +++++++++--- public/ng/js/gogs.js | 11 ++-- public/ng/js/min/gogs-min.js | 2 +- public/ng/less/gogs/settings.less | 9 ++- routers/user/setting.go | 75 ++++++++++++++++++++--- templates/.VERSION | 2 +- templates/user/settings/applications.tmpl | 56 +++++++++++++++++ templates/user/settings/nav.tmpl | 1 + templates/user/settings/sshkeys.tmpl | 8 ++- 20 files changed, 311 insertions(+), 62 deletions(-) create mode 100644 models/token.go delete mode 100644 modules/auth/publickey_form.go create mode 100644 templates/user/settings/applications.tmpl diff --git a/cmd/web.go b/cmd/web.go index b5690c87c..6b0ee9ef9 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -64,7 +64,7 @@ func checkVersion() { // Check dependency version. macaronVer := git.MustParseVersion(strings.Join(strings.Split(macaron.Version(), ".")[:3], ".")) - if macaronVer.LessThan(git.MustParseVersion("0.4.0")) { + if macaronVer.LessThan(git.MustParseVersion("0.4.2")) { log.Fatal(4, "Package macaron version is too old, did you forget to update?(github.com/Unknwon/macaron)") } i18nVer := git.MustParseVersion(i18n.Version()) @@ -199,6 +199,7 @@ func runWeb(*cli.Context) { m.Get("/ssh", user.SettingsSSHKeys) m.Post("/ssh", bindIgnErr(auth.AddSSHKeyForm{}), user.SettingsSSHKeysPost) m.Get("/social", user.SettingsSocial) + m.Combo("/applications").Get(user.SettingsApplications).Post(bindIgnErr(auth.NewAccessTokenForm{}), user.SettingsApplicationsPost) m.Route("/delete", "GET,POST", user.SettingsDelete) }, reqSignIn) m.Group("/user", func() { @@ -210,9 +211,6 @@ func runWeb(*cli.Context) { m.Get("/logout", user.SignOut) }) - // FIXME: Legacy - m.Get("/user/:username", ignSignIn, user.Profile) - // Gravatar service. avt := avatar.CacheServer("public/img/avatar/", "public/img/avatar_default.jpg") os.MkdirAll("public/img/avatar/", os.ModePerm) diff --git a/conf/locale/locale_en-US.ini b/conf/locale/locale_en-US.ini index 53bf0eb7d..5c39340bb 100644 --- a/conf/locale/locale_en-US.ini +++ b/conf/locale/locale_en-US.ini @@ -184,6 +184,7 @@ profile = Profile password = Password ssh_keys = SSH Keys social = Social Accounts +applications = Applications orgs = Organizations delete = Delete Account uid = Uid @@ -224,6 +225,16 @@ social_desc = This is a list of associated social accounts. Remove any binding t unbind = Unbind unbind_success = Social account has been unbound. +manage_access_token = Manage Personal Access Tokens +generate_new_token = Generate New Token +tokens_desc = Tokens you have generated that can be used to access the Gogs API. +new_token_desc = As for now, every token will have full access to your account. +token_name = Token Name +generate_token = Generate Token +generate_token_succees = New access token has been generated successfully! Make sure to copy your new personal access token now. You won't be able to see it again! +delete_token = Delete +delete_token_success = Personal access token has been deleted successfully! Don't forget to update your applications as well. + delete_account = Delete Your Account delete_prompt = The operation will delete your account permanently, and CANNOT be undone! confirm_delete_account = Confirm Deletion diff --git a/gogs.go b/gogs.go index bbf90fc56..ff6e25688 100644 --- a/gogs.go +++ b/gogs.go @@ -17,7 +17,7 @@ import ( "github.com/gogits/gogs/modules/setting" ) -const APP_VER = "0.5.7.1110 Beta" +const APP_VER = "0.5.8.1112 Beta" func init() { runtime.GOMAXPROCS(runtime.NumCPU()) diff --git a/models/models.go b/models/models.go index b4f5f0a2d..92a579dfc 100644 --- a/models/models.go +++ b/models/models.go @@ -39,7 +39,8 @@ var ( ) func init() { - tables = append(tables, new(User), new(PublicKey), new(Follow), new(Oauth2), + tables = append(tables, + new(User), new(PublicKey), new(Follow), new(Oauth2), new(AccessToken), new(Repository), new(Watch), new(Star), new(Action), new(Access), new(Issue), new(Comment), new(Attachment), new(IssueUser), new(Label), new(Milestone), new(Mirror), new(Release), new(LoginSource), new(Webhook), diff --git a/models/publickey.go b/models/publickey.go index 762d7333f..f5e487fdb 100644 --- a/models/publickey.go +++ b/models/publickey.go @@ -236,10 +236,10 @@ func GetPublicKeyById(keyId int64) (*PublicKey, error) { return key, nil } -// ListPublicKey returns a list of all public keys that user has. -func ListPublicKey(uid int64) ([]*PublicKey, error) { +// ListPublicKeys returns a list of public keys belongs to given user. +func ListPublicKeys(uid int64) ([]*PublicKey, error) { keys := make([]*PublicKey, 0, 5) - err := x.Find(&keys, &PublicKey{OwnerId: uid}) + err := x.Where("owner_id=?", uid).Find(&keys) if err != nil { return nil, err } diff --git a/models/token.go b/models/token.go new file mode 100644 index 000000000..909d05e07 --- /dev/null +++ b/models/token.go @@ -0,0 +1,69 @@ +// 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 ( + "errors" + "time" + + "github.com/gogits/gogs/modules/base" + "github.com/gogits/gogs/modules/uuid" +) + +var ( + ErrAccessTokenNotExist = errors.New("Access token does not exist") +) + +// AccessToken represents a personal access token. +type AccessToken struct { + Id int64 + Uid int64 + Name string + Sha1 string `xorm:"UNIQUE VARCHAR(40)"` + Created time.Time `xorm:"CREATED"` + Updated time.Time + HasRecentActivity bool `xorm:"-"` + HasUsed bool `xorm:"-"` +} + +// NewAccessToken creates new access token. +func NewAccessToken(t *AccessToken) error { + t.Sha1 = base.EncodeSha1(uuid.NewV4().String()) + _, err := x.Insert(t) + return err +} + +// GetAccessTokenBySha returns access token by given sha1. +func GetAccessTokenBySha(sha string) (*AccessToken, error) { + t := &AccessToken{Sha1: sha} + has, err := x.Get(t) + if err != nil { + return nil, err + } else if !has { + return nil, ErrAccessTokenNotExist + } + return t, nil +} + +// ListAccessTokens returns a list of access tokens belongs to given user. +func ListAccessTokens(uid int64) ([]*AccessToken, error) { + tokens := make([]*AccessToken, 0, 5) + err := x.Where("uid=?", uid).Desc("id").Find(&tokens) + if err != nil { + return nil, err + } + + for _, t := range tokens { + t.HasUsed = t.Updated.After(t.Created) + t.HasRecentActivity = t.Updated.Add(7 * 24 * time.Hour).After(time.Now()) + } + return tokens, nil +} + +// DeleteAccessTokenById deletes access token by given ID. +func DeleteAccessTokenById(id int64) error { + _, err := x.Id(id).Delete(new(AccessToken)) + return err +} diff --git a/modules/auth/auth.go b/modules/auth/auth.go index b9266d689..1a7606a79 100644 --- a/modules/auth/auth.go +++ b/modules/auth/auth.go @@ -20,7 +20,7 @@ import ( ) // SignedInId returns the id of signed in user. -func SignedInId(header http.Header, sess session.Store) int64 { +func SignedInId(req *http.Request, sess session.Store) int64 { if !models.HasEngine { return 0 } @@ -38,20 +38,38 @@ func SignedInId(header http.Header, sess session.Store) int64 { } return id } + + // API calls also need to check access token. + if strings.HasPrefix(req.URL.Path, "/api/") { + auHead := req.Header.Get("Authorization") + if len(auHead) > 0 { + auths := strings.Fields(auHead) + if len(auths) == 2 && auths[0] == "token" { + t, err := models.GetAccessTokenBySha(auths[1]) + if err != nil { + if err != models.ErrAccessTokenNotExist { + log.Error(4, "GetAccessTokenBySha: %v", err) + } + return 0 + } + return t.Uid + } + } + } return 0 } // SignedInUser returns the user object of signed user. -func SignedInUser(header http.Header, sess session.Store) *models.User { +func SignedInUser(req *http.Request, sess session.Store) *models.User { if !models.HasEngine { return nil } - uid := SignedInId(header, sess) + uid := SignedInId(req, sess) if uid <= 0 { if setting.Service.EnableReverseProxyAuth { - webAuthUser := header.Get(setting.ReverseProxyAuthUser) + webAuthUser := req.Header.Get(setting.ReverseProxyAuthUser) if len(webAuthUser) > 0 { u, err := models.GetUserByName(webAuthUser) if err != nil { @@ -65,7 +83,7 @@ func SignedInUser(header http.Header, sess session.Store) *models.User { } // Check with basic auth. - baHead := header.Get("Authorization") + baHead := req.Header.Get("Authorization") if len(baHead) > 0 { auths := strings.Fields(baHead) if len(auths) == 2 && auths[0] == "Basic" { diff --git a/modules/auth/publickey_form.go b/modules/auth/publickey_form.go deleted file mode 100644 index 5a1d44c04..000000000 --- a/modules/auth/publickey_form.go +++ /dev/null @@ -1,19 +0,0 @@ -// 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 auth - -import ( - "github.com/Unknwon/macaron" - "github.com/macaron-contrib/binding" -) - -type AddSSHKeyForm struct { - SSHTitle string `form:"title" binding:"Required"` - Content string `form:"content" binding:"Required"` -} - -func (f *AddSSHKeyForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { - return validate(errs, ctx.Data, f, ctx.Locale) -} diff --git a/modules/auth/user_form.go b/modules/auth/user_form.go index 72bdd4589..6046a8d1e 100644 --- a/modules/auth/user_form.go +++ b/modules/auth/user_form.go @@ -95,3 +95,20 @@ type ChangePasswordForm struct { func (f *ChangePasswordForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { return validate(errs, ctx.Data, f, ctx.Locale) } + +type AddSSHKeyForm struct { + SSHTitle string `form:"title" binding:"Required"` + Content string `form:"content" binding:"Required"` +} + +func (f *AddSSHKeyForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +type NewAccessTokenForm struct { + Name string `form:"name" binding:"Required"` +} + +func (f *NewAccessTokenForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} diff --git a/modules/base/tool.go b/modules/base/tool.go index 4d3e1c7bf..50f073a52 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -33,6 +33,13 @@ func EncodeMd5(str string) string { return hex.EncodeToString(m.Sum(nil)) } +// Encode string to sha1 hex value. +func EncodeSha1(str string) string { + h := sha1.New() + h.Write([]byte(str)) + return hex.EncodeToString(h.Sum(nil)) +} + func BasicAuthDecode(encoded string) (user string, name string, err error) { var s []byte s, err = base64.StdEncoding.DecodeString(encoded) diff --git a/modules/middleware/context.go b/modules/middleware/context.go index 58b4f7497..cbc0b0cf3 100644 --- a/modules/middleware/context.go +++ b/modules/middleware/context.go @@ -172,7 +172,7 @@ func Contexter() macaron.Handler { ctx.Data["PageStartTime"] = time.Now() // Get user from session if logined. - ctx.User = auth.SignedInUser(ctx.Req.Header, ctx.Session) + ctx.User = auth.SignedInUser(ctx.Req.Request, ctx.Session) if ctx.User != nil { ctx.IsSigned = true diff --git a/public/ng/css/gogs.css b/public/ng/css/gogs.css index a97e6ce1a..1cf6cb3bc 100644 --- a/public/ng/css/gogs.css +++ b/public/ng/css/gogs.css @@ -1699,18 +1699,21 @@ The register and sign-in page style #repo-hooks-panel, #repo-hooks-history-panel, #user-social-panel, +#user-applications-panel, #user-ssh-panel { margin-bottom: 20px; } #repo-hooks-panel .setting-list, #repo-hooks-history-panel .setting-list, #user-social-panel .setting-list, +#user-applications-panel .setting-list, #user-ssh-panel .setting-list { background-color: #FFF; } #repo-hooks-panel .setting-list li, #repo-hooks-history-panel .setting-list li, #user-social-panel .setting-list li, +#user-applications-panel .setting-list li, #user-ssh-panel .setting-list li { padding: 8px 20px; border-bottom: 1px solid #eaeaea; @@ -1718,18 +1721,21 @@ The register and sign-in page style #repo-hooks-panel .setting-list li.ssh:hover, #repo-hooks-history-panel .setting-list li.ssh:hover, #user-social-panel .setting-list li.ssh:hover, +#user-applications-panel .setting-list li.ssh:hover, #user-ssh-panel .setting-list li.ssh:hover { background-color: #ffffEE; } #repo-hooks-panel .setting-list li i, #repo-hooks-history-panel .setting-list li i, #user-social-panel .setting-list li i, +#user-applications-panel .setting-list li i, #user-ssh-panel .setting-list li i { padding-right: 5px; } #repo-hooks-panel .active-icon, #repo-hooks-history-panel .active-icon, #user-social-panel .active-icon, +#user-applications-panel .active-icon, #user-ssh-panel .active-icon { width: 10px; height: 10px; @@ -1741,43 +1747,60 @@ The register and sign-in page style #repo-hooks-panel .ssh-content, #repo-hooks-history-panel .ssh-content, #user-social-panel .ssh-content, +#user-applications-panel .ssh-content, #user-ssh-panel .ssh-content { margin-left: 24px; } #repo-hooks-panel .ssh-content .octicon, #repo-hooks-history-panel .ssh-content .octicon, #user-social-panel .ssh-content .octicon, +#user-applications-panel .ssh-content .octicon, #user-ssh-panel .ssh-content .octicon { margin-right: 4px; } #repo-hooks-panel .ssh-content .print, #repo-hooks-history-panel .ssh-content .print, #user-social-panel .ssh-content .print, +#user-applications-panel .ssh-content .print, #user-ssh-panel .ssh-content .print, +#repo-hooks-panel .ssh-content .access, +#repo-hooks-history-panel .ssh-content .access, +#user-social-panel .ssh-content .access, +#user-applications-panel .ssh-content .access, +#user-ssh-panel .ssh-content .access, #repo-hooks-panel .ssh-content .activity, #repo-hooks-history-panel .ssh-content .activity, #user-social-panel .ssh-content .activity, +#user-applications-panel .ssh-content .activity, #user-ssh-panel .ssh-content .activity { color: #888; } -#repo-hooks-panel .ssh-delete-btn, -#repo-hooks-history-panel .ssh-delete-btn, -#user-social-panel .ssh-delete-btn, -#user-ssh-panel .ssh-delete-btn { +#repo-hooks-panel .ssh-content .access, +#repo-hooks-history-panel .ssh-content .access, +#user-social-panel .ssh-content .access, +#user-applications-panel .ssh-content .access, +#user-ssh-panel .ssh-content .access { + max-width: 500px; +} +#repo-hooks-panel .ssh-btn, +#repo-hooks-history-panel .ssh-btn, +#user-social-panel .ssh-btn, +#user-applications-panel .ssh-btn, +#user-ssh-panel .ssh-btn { margin-top: 6px; } -#user-ssh-add-form .panel-body { +.form-settings-add .panel-body { background-color: #FFF; padding: 30px 0; } -#user-ssh-add-form .ipt { +.form-settings-add .ipt { width: 500px; } -#user-ssh-add-form textarea { +.form-settings-add textarea { height: 120px; margin-left: 3px; } -#user-ssh-add-form .field { +.form-settings-add .field { margin-bottom: 24px; } .pr-main { diff --git a/public/ng/js/gogs.js b/public/ng/js/gogs.js index eba1744b9..333263762 100644 --- a/public/ng/js/gogs.js +++ b/public/ng/js/gogs.js @@ -300,8 +300,11 @@ function initCore() { $.magnificPopup.close(); }); - // Collapse. + // Plugins. $('.collapse').hide(); + $('.tipsy-tooltip').tipsy({ + fade: true + }); } function initUserSetting() { @@ -329,9 +332,9 @@ function initUserSetting() { $profile_form.submit(); }); - // Show add SSH key panel. - $('#ssh-add').click(function () { - $('#user-ssh-add-form').removeClass("hide"); + // Show panels. + $('.show-form-btn').click(function () { + $($(this).data('target-form')).removeClass("hide"); }); // Confirmation of delete account. diff --git a/public/ng/js/min/gogs-min.js b/public/ng/js/min/gogs-min.js index 6b7f408d2..392325c78 100644 --- a/public/ng/js/min/gogs-min.js +++ b/public/ng/js/min/gogs-min.js @@ -1,4 +1,4 @@ -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()}),$(document).on("click",".popup-modal-dismiss",function(e){e.preventDefault(),$.magnificPopup.close()}),$(".collapse").hide()}function initUserSetting(){var t=$("#username"),n=$("#user-profile-form");$("#change-username-btn").magnificPopup({modal:!0,callbacks:{open:function(){t.data("uname")==t.val()&&($.magnificPopup.close(),n.submit())}}}).click(function(){return t.data("uname")!=t.val()?(e.preventDefault(),!0):void 0}),$("#change-username-submit").click(function(){$.magnificPopup.close(),n.submit()}),$("#ssh-add").click(function(){$("#user-ssh-add-form").removeClass("hide")}),$("#delete-account-btn").magnificPopup({modal:!0}).click(function(e){return e.preventDefault(),!0}),$("#delete-account-submit").click(function(){$.magnificPopup.close(),$("#delete-account-form").submit()})}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(){var t=$("#repo_name"),n=$("#repo-setting-form");$("#change-reponame-btn").magnificPopup({modal:!0,callbacks:{open:function(){t.data("repo-name")==t.val()&&($.magnificPopup.close(),n.submit())}}}).click(function(){return t.data("repo-name")!=t.val()?(e.preventDefault(),!0):void 0}),$("#change-reponame-submit").click(function(){$.magnificPopup.close(),n.submit()}),initHookTypeChange(),$("#transfer-repo-btn").magnificPopup({modal:!0}),$("#transfer-repo-submit").click(function(){$.magnificPopup.close(),$("#transfer-repo-form").submit()}),$("#delete-repo-btn").magnificPopup({modal:!0}),$("#delete-repo-submit").click(function(){$.magnificPopup.close(),$("#delete-repo-form").submit()}),$("#repo-collab-list hr:last-child").remove();var r=$("#repo-collaborator").next().next().find("ul");$("#repo-collaborator").on("keyup",function(){var e=$(this);return e.val()?void Gogs.searchUsers(e.val(),r):void r.toggleHide()}).on("focus",function(){$(this).val()?r.toggleShow():r.toggleHide()}).next().next().find("ul").on("click","li",function(){$("#repo-collaborator").val($(this).text()),r.toggleHide()})}function initOrgSetting(){var t=$("#orgname"),n=$("#org-setting-form");$("#change-orgname-btn").magnificPopup({modal:!0,callbacks:{open:function(){t.data("orgname")==t.val()&&($.magnificPopup.close(),n.submit())}}}).click(function(){return t.data("orgname")!=t.val()?(e.preventDefault(),!0):void 0}),$("#change-orgname-submit").click(function(){$.magnificPopup.close(),n.submit()}),$("#delete-org-btn").magnificPopup({modal:!0}).click(function(e){return e.preventDefault(),!0}),$("#delete-org-submit").click(function(){$.magnificPopup.close(),$("#delete-org-form").submit()}),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").magnificPopup({modal:!0}).click(function(e){return e.preventDefault(),!0}),$("#delete-team-submit").click(function(){$.magnificPopup.close();var e=$("#team-create-form");e.attr("action",e.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())}),$("#delete-account-btn").magnificPopup({modal:!0}).click(function(e){return e.preventDefault(),!0}),$("#delete-account-submit").click(function(){$.magnificPopup.close();var e=$("#user-profile-form");e.attr("action",e.data("delete-url"))}),$("#auth-type").on("change",function(){var e=$(this).val();2==e&&($(".ldap").toggleShow(),$(".smtp").toggleHide()),3==e&&($(".smtp").toggleShow(),$(".ldap").toggleHide())}),$("#delete-auth-btn").magnificPopup({modal:!0}).click(function(e){return e.preventDefault(),!0}),$("#delete-auth-submit").click(function(){$.magnificPopup.close();var e=$("#auth-setting-form");e.attr("action",e.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 initTimeSwitch(){$(".time-since[title]").on("click",function(){var e=$(this),t=e.attr("title"),n=e.text();e.text(t),e.attr("title",n)})}function initDiff(){$(".diff-detail-box>a").click(function(){$($(this).data("target")).slideToggle(100)});var e=$(".diff-counter");e.length<1||e.each(function(e,t){var n=$(t),r=n.find("span[data-line].add").data("line"),i=n.find("span[data-line].del").data("line"),o=parseFloat(r)/(parseFloat(r)+parseFloat(i))*100;n.find(".bar .add").css("width",o+"%")})}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($t,"-$1").toLowerCase();if(n=e.getAttribute(r),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:Tt.test(n)?ot.parseJSON(n):n}catch(i){}ot.data(e,t,n)}else n=void 0}return n}function c(e){var t;for(t in e)if(("data"!==t||!ot.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}function u(e,t,n,r){if(ot.acceptData(e)){var i,o,a=ot.expando,s=e.nodeType,l=s?ot.cache:e,c=s?e[a]:e[a]&&a;if(c&&l[c]&&(r||l[c].data)||void 0!==n||"string"!=typeof t)return c||(c=s?e[a]=V.pop()||ot.guid++:a),l[c]||(l[c]=s?{}:{toJSON:ot.noop}),("object"==typeof t||"function"==typeof t)&&(r?l[c]=ot.extend(l[c],t):l[c].data=ot.extend(l[c].data,t)),o=l[c],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?!c(r):!ot.isEmptyObject(r))return}(n||(delete a[s].data,c(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){Dt.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&&Dt.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 k(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 T(e){var t=mt,n=Jt[e];return n||(n=k(e,t),"none"!==n&&n||(Kt=(Kt||ot("