From 828c89de996c328bb3759d90be8a8a35b4d9cdc0 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 16 Mar 2014 02:28:24 -0400 Subject: [PATCH 01/12] Clean code --- README.md | 1 + gogs.go | 22 ++++++++++------------ models/repo.go | 2 +- modules/middleware/repo.go | 14 +++++--------- serve.go | 2 +- templates/repo/create.tmpl | 2 +- web.go | 2 -- 7 files changed, 19 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 526cc501b2..0fb646e5e0 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Please see [Wiki](https://github.com/gogits/gogs/wiki) for project design, devel ## Features +- Activity timeline - SSH protocal support. - Register/delete account. - Create/delete public repository. diff --git a/gogs.go b/gogs.go index e826ab9f36..08421f0055 100644 --- a/gogs.go +++ b/gogs.go @@ -7,7 +7,7 @@ package main import ( "os" - "os/user" + // "os/user" "runtime" "github.com/codegangsta/cli" @@ -27,14 +27,14 @@ func init() { runtime.GOMAXPROCS(runtime.NumCPU()) } -func checkRunUser() bool { - u, err := user.Current() - if err != nil { - // TODO: log - return false - } - return u.Username == base.Cfg.MustValue("", "RUN_USER") -} +// func checkRunUser() bool { +// u, err := user.Current() +// if err != nil { +// // TODO: log +// return false +// } +// return u.Username == base.Cfg.MustValue("", "RUN_USER") +// } func main() { /*if !checkRunUser() { @@ -50,8 +50,6 @@ func main() { CmdWeb, CmdServ, } - app.Flags = append(app.Flags, []cli.Flag{ - cli.BoolFlag{"noterm", "disable color output"}, - }...) + app.Flags = append(app.Flags, []cli.Flag{}...) app.Run(os.Args) } diff --git a/models/repo.go b/models/repo.go index 125abfc342..56e286ec4a 100644 --- a/models/repo.go +++ b/models/repo.go @@ -253,7 +253,7 @@ func GetRepositoryById(id int64) (repo *Repository, err error) { // GetRepositories returns the list of repositories of given user. func GetRepositories(user *User) ([]Repository, error) { repos := make([]Repository, 0, 10) - err := orm.Find(&repos, &Repository{OwnerId: user.Id}) + err := orm.Desc("updated").Find(&repos, &Repository{OwnerId: user.Id}) return repos, err } diff --git a/modules/middleware/repo.go b/modules/middleware/repo.go index 948713efe8..8cdc6df718 100644 --- a/modules/middleware/repo.go +++ b/modules/middleware/repo.go @@ -5,6 +5,8 @@ package middleware import ( + "errors" + "github.com/codegangsta/martini" "github.com/gogits/gogs/models" @@ -31,9 +33,7 @@ func RepoAssignment(redirect bool) martini.Handler { ctx.Render.Redirect("/") return } - //data["ErrorMsg"] = err - //log.Error("repo.Single: %v", err) - //r.HTML(200, "base/error", data) + ctx.Handle(200, "RepoAssignment", err) return } } else { @@ -45,9 +45,7 @@ func RepoAssignment(redirect bool) martini.Handler { ctx.Render.Redirect("/") return } - //data["ErrorMsg"] = "invliad user account for single repository" - //log.Error("repo.Single: %v", err) - //r.HTML(200, "base/error", data) + ctx.Handle(200, "RepoAssignment", errors.New("invliad user account for single repository")) return } @@ -60,9 +58,7 @@ func RepoAssignment(redirect bool) martini.Handler { ctx.Render.Redirect("/") return } - //data["ErrorMsg"] = err - //log.Error("repo.Single: %v", err) - //r.HTML(200, "base/error", data) + ctx.Handle(200, "RepoAssignment", err) return } diff --git a/serve.go b/serve.go index 56105f2f80..68455de3d3 100644 --- a/serve.go +++ b/serve.go @@ -58,7 +58,7 @@ func runServ(*cli.Context) { cmd := os.Getenv("SSH_ORIGINAL_COMMAND") if cmd == "" { - println("Hi ", user.Name, "! You've successfully authenticated, but Gogs does not provide shell access.") + println("Hi", user.Name, "! You've successfully authenticated, but Gogs does not provide shell access.") return } diff --git a/templates/repo/create.tmpl b/templates/repo/create.tmpl index 93d33c8744..2de92f515f 100644 --- a/templates/repo/create.tmpl +++ b/templates/repo/create.tmpl @@ -59,7 +59,7 @@
diff --git a/web.go b/web.go index 3f0e0ef7e2..b7138120a3 100644 --- a/web.go +++ b/web.go @@ -74,8 +74,6 @@ func runWeb(*cli.Context) { middleware.SignInRequire(false), middleware.RepoAssignment(true), repo.Single) m.Get("/:username/:reponame", middleware.SignInRequire(false), middleware.RepoAssignment(true), repo.Single) - //m.Get("/:username/:reponame", repo.Repo) - listenAddr := fmt.Sprintf("%s:%s", base.Cfg.MustValue("server", "HTTP_ADDR"), base.Cfg.MustValue("server", "HTTP_PORT", "3000")) From c3e0554fd77f976b58a8da10d4878353bb895a78 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 16 Mar 2014 02:53:41 -0400 Subject: [PATCH 02/12] Clean code --- gogs.go | 2 +- models/action.go | 4 ++-- update.go | 27 ++++++++++++++++++++------- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/gogs.go b/gogs.go index 315297b101..9d1f2032d6 100644 --- a/gogs.go +++ b/gogs.go @@ -20,7 +20,7 @@ import ( // Test that go1.1 tag above is included in builds. main.go refers to this definition. const go11tag = true -const APP_VER = "0.0.8.0315" +const APP_VER = "0.0.8.0316.1" func init() { base.AppVer = APP_VER diff --git a/models/action.go b/models/action.go index 93c1e2768f..9e075646a6 100644 --- a/models/action.go +++ b/models/action.go @@ -43,6 +43,7 @@ func (a Action) GetRepoName() string { return a.RepoName } +// CommitRepoAction records action for commit repository. func CommitRepoAction(userId int64, userName string, repoId int64, repoName string, msg string) error { _, err := orm.InsertOne(&Action{ @@ -57,8 +58,7 @@ func CommitRepoAction(userId int64, userName string, return err } -// NewRepoAction inserts action for create repository. - +// NewRepoAction records action for create repository. func NewRepoAction(user *User, repo *Repository) error { _, err := orm.InsertOne(&Action{ UserId: user.Id, diff --git a/update.go b/update.go index 339b3ab94f..477989e861 100644 --- a/update.go +++ b/update.go @@ -1,13 +1,19 @@ +// 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 main import ( "os" "strconv" - "github.com/gogits/gogs/models" - "github.com/codegangsta/cli" + git "github.com/gogits/git" + + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/modules/log" ) var CmdUpdate = cli.Command{ @@ -41,11 +47,18 @@ func runUpdate(*cli.Context) { if err != nil { return } - sUserId, _ := strconv.Atoi(userId) - sRepoId, _ := strconv.Atoi(repoId) - err = models.CommitRepoAction(int64(sUserId), userName, - int64(sRepoId), repoName, lastCommit.Message()) + sUserId, err := strconv.Atoi(userId) if err != nil { - //TODO: log + log.Error("runUpdate.Parse userId: %v", err) + return + } + sRepoId, err := strconv.Atoi(repoId) + if err != nil { + log.Error("runUpdate.Parse repoId: %v", err) + return + } + if err = models.CommitRepoAction(int64(sUserId), userName, + int64(sRepoId), repoName, lastCommit.Message()); err != nil { + log.Error("runUpdate.models.CommitRepoAction: %v", err) } } From 8de9517862acd77c27da015654fc236a6722d188 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 16 Mar 2014 03:41:22 -0400 Subject: [PATCH 03/12] Mirror fix --- routers/dashboard.go | 4 ++++ routers/repo/single.go | 12 ++++++++++++ routers/user/user.go | 12 ++++++++++++ templates/base/navbar.tmpl | 3 +-- templates/repo/setting.tmpl | 2 +- templates/repo/single.tmpl | 6 +++--- web.go | 8 ++++++++ 7 files changed, 41 insertions(+), 6 deletions(-) diff --git a/routers/dashboard.go b/routers/dashboard.go index cab1662fc3..ddaef0cb56 100644 --- a/routers/dashboard.go +++ b/routers/dashboard.go @@ -17,3 +17,7 @@ func Home(ctx *middleware.Context) { ctx.Data["PageIsHome"] = true ctx.Render.HTML(200, "home", ctx.Data) } + +func Help(ctx *middleware.Context) string { + return "This is help page" +} diff --git a/routers/repo/single.go b/routers/repo/single.go index a7f07898f0..a0f444716c 100644 --- a/routers/repo/single.go +++ b/routers/repo/single.go @@ -60,3 +60,15 @@ func Setting(ctx *middleware.Context) { ctx.Data["IsRepoToolbarSetting"] = true ctx.Render.HTML(200, "repo/setting", ctx.Data) } + +func Commits(ctx *middleware.Context) string { + return "This is commits page" +} + +func Issues(ctx *middleware.Context) string { + return "This is issues page" +} + +func Pulls(ctx *middleware.Context) string { + return "This is pulls page" +} diff --git a/routers/user/user.go b/routers/user/user.go index 8d8691a39f..e13f6909dd 100644 --- a/routers/user/user.go +++ b/routers/user/user.go @@ -189,3 +189,15 @@ func Feeds(ctx *middleware.Context, form auth.FeedsForm) { } ctx.Render.JSON(200, &feeds) } + +func Issues(ctx *middleware.Context) string { + return "This is issues page" +} + +func Pulls(ctx *middleware.Context) string { + return "This is pulls page" +} + +func Stars(ctx *middleware.Context) string { + return "This is stars page" +} diff --git a/templates/base/navbar.tmpl b/templates/base/navbar.tmpl index 3fe0c1147a..181beb5a44 100644 --- a/templates/base/navbar.tmpl +++ b/templates/base/navbar.tmpl @@ -3,8 +3,7 @@
- repo-options +

Repository Options

{{$paths := .Paths}} @@ -47,7 +47,7 @@ {{if .IsDir}} {{.Name}} {{else}} - {{.Name}} - {{FileSize .Size}} + {{.Name}} - {{FileSize .Size}} {{end}} {{.Message}} diff --git a/web.go b/web.go index b7138120a3..16f39bdb0e 100644 --- a/web.go +++ b/web.go @@ -50,6 +50,9 @@ func runWeb(*cli.Context) { // Routers. m.Get("/", middleware.SignInRequire(false), routers.Home) + m.Get("/issues", middleware.SignInRequire(true), user.Issues) + m.Get("/pulls", middleware.SignInRequire(true), user.Pulls) + m.Get("/stars", middleware.SignInRequire(true), user.Stars) m.Any("/user/login", middleware.SignOutRequire(), binding.BindIgnErr(auth.LogInForm{}), user.SignIn) m.Any("/user/logout", middleware.SignInRequire(true), user.SignOut) m.Any("/user/sign_up", middleware.SignOutRequire(), binding.BindIgnErr(auth.RegisterForm{}), user.SignUp) @@ -67,7 +70,12 @@ func runWeb(*cli.Context) { m.Any("/repo/create", middleware.SignInRequire(true), binding.BindIgnErr(auth.CreateRepoForm{}), repo.Create) m.Any("/repo/delete", middleware.SignInRequire(true), binding.Bind(auth.DeleteRepoForm{}), repo.Delete) + m.Get("/help", routers.Help) + m.Get("/:username/:reponame/settings", middleware.SignInRequire(false), middleware.RepoAssignment(true), repo.Setting) + m.Get("/:username/:reponame/commits", middleware.SignInRequire(false), middleware.RepoAssignment(true), repo.Commits) + m.Get("/:username/:reponame/issues", middleware.SignInRequire(false), middleware.RepoAssignment(true), repo.Issues) + m.Get("/:username/:reponame/pulls", middleware.SignInRequire(false), middleware.RepoAssignment(true), repo.Pulls) m.Get("/:username/:reponame/tree/:branchname/**", middleware.SignInRequire(false), middleware.RepoAssignment(true), repo.Single) m.Get("/:username/:reponame/tree/:branchname", From ab747f279088c9ed6114c4227c71173ebd1e6f00 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 16 Mar 2014 05:24:13 -0400 Subject: [PATCH 04/12] Fix delete SSH key in file --- conf/app.ini | 4 +- conf/gitignore/Ruby | 18 ++++++++ conf/license/MIT License | 21 ++++++++++ models/publickey.go | 88 ++++++++++++++++++++++++++++++++++++---- models/user.go | 12 ++++++ routers/repo/single.go | 4 ++ 6 files changed, 138 insertions(+), 9 deletions(-) create mode 100644 conf/gitignore/Ruby create mode 100644 conf/license/MIT License diff --git a/conf/app.ini b/conf/app.ini index 3e5a126f89..f8ff81db74 100644 --- a/conf/app.ini +++ b/conf/app.ini @@ -3,8 +3,8 @@ RUN_USER = lunny [repository] ROOT = /Users/%(RUN_USER)s/git/gogs-repositories -LANG_IGNS=Google Go|C|Python -LICENSES=Apache v2 License|GPL v2|BSD (3-Clause) License +LANG_IGNS=Google Go|C|Python|Ruby +LICENSES=Apache v2 License|GPL v2|MIT License|BSD (3-Clause) License [server] HTTP_ADDR = diff --git a/conf/gitignore/Ruby b/conf/gitignore/Ruby new file mode 100644 index 0000000000..eb76b24855 --- /dev/null +++ b/conf/gitignore/Ruby @@ -0,0 +1,18 @@ +*.gem +*.rbc +.bundle +.config +coverage +InstalledFiles +lib/bundler/man +pkg +rdoc +spec/reports +test/tmp +test/version_tmp +tmp + +# YARD artifacts +.yardoc +_yardoc +doc/ \ No newline at end of file diff --git a/conf/license/MIT License b/conf/license/MIT License new file mode 100644 index 0000000000..be1d1ae048 --- /dev/null +++ b/conf/license/MIT License @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/models/publickey.go b/models/publickey.go index 49d7f30828..2f32dc3f09 100644 --- a/models/publickey.go +++ b/models/publickey.go @@ -1,21 +1,30 @@ +// 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 ( + "bufio" "fmt" + "io" "os" "os/exec" "path/filepath" + "strings" + "sync" "time" "github.com/Unknwon/com" ) var ( + sshOpLocker = sync.Mutex{} //publicKeyRootPath string - sshPath string - appPath string - tmplPublicKey = "### autogenerated by gitgos, DO NOT EDIT\n" + - "command=\"%s serv key-%d\",no-port-forwarding," + + sshPath string + appPath string + // "### autogenerated by gitgos, DO NOT EDIT\n" + tmplPublicKey = "command=\"%s serv key-%d\",no-port-forwarding," + "no-X11-forwarding,no-agent-forwarding,no-pty %s\n" ) @@ -77,9 +86,69 @@ func AddPublicKey(key *PublicKey) error { return nil } -func DeletePublicKey(key *PublicKey) error { - _, err := orm.Delete(key) - return err +// DeletePublicKey deletes SSH key information both in database and authorized_keys file. +func DeletePublicKey(key *PublicKey) (err error) { + if _, err = orm.Delete(key); err != nil { + return err + } + + sshOpLocker.Lock() + defer sshOpLocker.Unlock() + + p := filepath.Join(sshPath, "authorized_keys") + tmpP := filepath.Join(sshPath, "authorized_keys.tmp") + fr, err := os.Open(p) + if err != nil { + return err + } + defer fr.Close() + + fw, err := os.Create(tmpP) + if err != nil { + return err + } + defer fw.Close() + + buf := bufio.NewReader(fr) + for { + line, errRead := buf.ReadString('\n') + line = strings.TrimSpace(line) + + if errRead != nil { + if errRead != io.EOF { + return errRead + } + + // Reached end of file, if nothing to read then break, + // otherwise handle the last line. + if len(line) == 0 { + break + } + } + + // Found the line and copy rest of file. + if strings.Contains(line, key.Content) { + if _, err = io.Copy(fw, fr); err != nil { + return err + } + break + } + + // Still finding the line, copy the line that currently read. + if _, err = fw.WriteString(line + "\n"); err != nil { + return err + } + + if errRead == io.EOF { + break + } + } + + if err = os.Remove(p); err != nil { + return err + } + + return os.Rename(tmpP, p) } func ListPublicKey(userId int64) ([]PublicKey, error) { @@ -89,11 +158,16 @@ func ListPublicKey(userId int64) ([]PublicKey, error) { } func SaveAuthorizedKeyFile(key *PublicKey) error { + sshOpLocker.Lock() + defer sshOpLocker.Unlock() + p := filepath.Join(sshPath, "authorized_keys") f, err := os.OpenFile(p, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) if err != nil { return err } + defer f.Close() + //os.Chmod(p, 0600) _, err = f.WriteString(GenAuthorizedKey(key.Id, key.Content)) return err diff --git a/models/user.go b/models/user.go index a0e4bb8004..ffeb72525d 100644 --- a/models/user.go +++ b/models/user.go @@ -142,6 +142,7 @@ func UpdateUser(user *User) (err error) { // DeleteUser completely deletes everything of the user. func DeleteUser(user *User) error { + // Check ownership of repository. count, err := GetRepositoryCount(user) if err != nil { return errors.New("modesl.GetRepositories: " + err.Error()) @@ -151,6 +152,17 @@ func DeleteUser(user *User) error { // TODO: check issues, other repos' commits + // Delete SSH keys. + keys := make([]PublicKey, 0, 10) + if err = orm.Find(&keys, &PublicKey{OwnerId: user.Id}); err != nil { + return err + } + for _, key := range keys { + if err = DeletePublicKey(&key); err != nil { + return err + } + } + _, err = orm.Delete(user) // TODO: delete and update follower information. return err diff --git a/routers/repo/single.go b/routers/repo/single.go index a0f444716c..eda30c00a6 100644 --- a/routers/repo/single.go +++ b/routers/repo/single.go @@ -1,3 +1,7 @@ +// 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 repo import ( From bcafba47e8c384a13315d998eb1ba17e9aaf16f9 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 16 Mar 2014 05:48:20 -0400 Subject: [PATCH 05/12] Bug fix --- models/publickey.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/models/publickey.go b/models/publickey.go index 2f32dc3f09..3171e268f7 100644 --- a/models/publickey.go +++ b/models/publickey.go @@ -6,6 +6,7 @@ package models import ( "bufio" + "errors" "fmt" "io" "os" @@ -88,6 +89,12 @@ func AddPublicKey(key *PublicKey) error { // DeletePublicKey deletes SSH key information both in database and authorized_keys file. func DeletePublicKey(key *PublicKey) (err error) { + has, err := orm.Id(key.Id).Get(key) + if err != nil { + return err + } else if !has { + return errors.New("Public key does not exist") + } if _, err = orm.Delete(key); err != nil { return err } @@ -128,12 +135,8 @@ func DeletePublicKey(key *PublicKey) (err error) { // Found the line and copy rest of file. if strings.Contains(line, key.Content) { - if _, err = io.Copy(fw, fr); err != nil { - return err - } - break + continue } - // Still finding the line, copy the line that currently read. if _, err = fw.WriteString(line + "\n"); err != nil { return err From c83657307e4ba5d414389c0e012ecf789053f763 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 16 Mar 2014 05:53:06 -0400 Subject: [PATCH 06/12] Fix delete SSH key in file --- models/publickey.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/publickey.go b/models/publickey.go index 3171e268f7..6aed7c5741 100644 --- a/models/publickey.go +++ b/models/publickey.go @@ -134,7 +134,7 @@ func DeletePublicKey(key *PublicKey) (err error) { } // Found the line and copy rest of file. - if strings.Contains(line, key.Content) { + if strings.Contains(line, fmt.Sprintf("key-%d", key.Id)) && strings.Contains(line, key.Content) { continue } // Still finding the line, copy the line that currently read. From ca956d5cec34fd99d88494af3cafb214606fe27b Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 16 Mar 2014 06:16:03 -0400 Subject: [PATCH 07/12] Add generate fingerprint of ssh key --- models/publickey.go | 46 +++++++++++++++++++++++++---------- templates/user/publickey.tmpl | 2 +- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/models/publickey.go b/models/publickey.go index 6aed7c5741..bd0b7ee937 100644 --- a/models/publickey.go +++ b/models/publickey.go @@ -11,6 +11,7 @@ import ( "io" "os" "os/exec" + "path" "path/filepath" "strings" "sync" @@ -57,29 +58,48 @@ func init() { } type PublicKey struct { - Id int64 - OwnerId int64 `xorm:"index"` - Name string `xorm:"unique not null"` - Content string `xorm:"text not null"` - Created time.Time `xorm:"created"` - Updated time.Time `xorm:"updated"` + Id int64 + OwnerId int64 `xorm:"index"` + Name string `xorm:"unique not null"` + Fingerprint string + Content string `xorm:"text not null"` + Created time.Time `xorm:"created"` + Updated time.Time `xorm:"updated"` } func GenAuthorizedKey(keyId int64, key string) string { return fmt.Sprintf(tmplPublicKey, appPath, keyId, key) } -func AddPublicKey(key *PublicKey) error { - _, err := orm.Insert(key) +func AddPublicKey(key *PublicKey) (err error) { + // Calculate fingerprint. + tmpPath := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()), + "id_rsa.pub") + os.MkdirAll(path.Dir(tmpPath), os.ModePerm) + f, err := os.Create(tmpPath) if err != nil { + return + } + if _, err = f.WriteString(key.Content); err != nil { + return err + } + f.Close() + stdout, _, err := com.ExecCmd("ssh-keygen", "-l", "-f", tmpPath) + if err != nil { + return err + } else if len(stdout) < 2 { + return errors.New("Not enough output for calculating fingerprint") + } + key.Fingerprint = strings.Split(stdout, " ")[1] + + // Save SSH key. + if _, err = orm.Insert(key); err != nil { return err } - err = SaveAuthorizedKeyFile(key) - if err != nil { - _, err2 := orm.Delete(key) - if err2 != nil { - // TODO: log the error + if err = SaveAuthorizedKeyFile(key); err != nil { + if _, err2 := orm.Delete(key); err2 != nil { + return err2 } return err } diff --git a/templates/user/publickey.tmpl b/templates/user/publickey.tmpl index 60d2c2464a..b671f63fdb 100644 --- a/templates/user/publickey.tmpl +++ b/templates/user/publickey.tmpl @@ -21,7 +21,7 @@
  • SSH Key's name
  • {{range .Keys}}
  • {{.Name}} - (print code) + ({{.Fingerprint}}) Delete
  • {{end}}
  • From fb960db6afa5fa84e60556f0c7d240b4af165a8d Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 16 Mar 2014 06:25:16 -0400 Subject: [PATCH 08/12] Add check if public key name has been used --- models/publickey.go | 12 ++++++++++++ routers/user/setting.go | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/models/publickey.go b/models/publickey.go index bd0b7ee937..ee6bd53101 100644 --- a/models/publickey.go +++ b/models/publickey.go @@ -67,11 +67,23 @@ type PublicKey struct { Updated time.Time `xorm:"updated"` } +var ( + ErrKeyAlreadyExist = errors.New("Public key already exist") +) + func GenAuthorizedKey(keyId int64, key string) string { return fmt.Sprintf(tmplPublicKey, appPath, keyId, key) } func AddPublicKey(key *PublicKey) (err error) { + // Check if public key name has been used. + has, err := orm.Get(key) + if err != nil { + return err + } else if has { + return ErrKeyAlreadyExist + } + // Calculate fingerprint. tmpPath := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()), "id_rsa.pub") diff --git a/routers/user/setting.go b/routers/user/setting.go index cd12bb6296..91e992b18e 100644 --- a/routers/user/setting.go +++ b/routers/user/setting.go @@ -128,6 +128,10 @@ func SettingSSHKeys(ctx *middleware.Context, form auth.AddSSHKeyForm) { } if err := models.AddPublicKey(k); err != nil { + if err.Error() == models.ErrKeyAlreadyExist.Error() { + ctx.RenderWithErr("Public key name has been used", "user/publickey", &form) + return + } ctx.Handle(200, "ssh.AddPublicKey", err) return } else { From 0754dd2f955c70994753cd18228333ca32ceee72 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 16 Mar 2014 06:38:39 -0400 Subject: [PATCH 09/12] Add delete all feeds when delete account --- models/user.go | 7 ++++++- routers/user/user.go | 2 +- templates/user/publickey.tmpl | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/models/user.go b/models/user.go index ffeb72525d..463d4a271f 100644 --- a/models/user.go +++ b/models/user.go @@ -152,7 +152,12 @@ func DeleteUser(user *User) error { // TODO: check issues, other repos' commits - // Delete SSH keys. + // Delete all feeds. + if _, err = orm.Delete(&Action{UserId: user.Id}); err != nil { + return err + } + + // Delete all SSH keys. keys := make([]PublicKey, 0, 10) if err = orm.Find(&keys, &PublicKey{OwnerId: user.Id}); err != nil { return err diff --git a/routers/user/user.go b/routers/user/user.go index e13f6909dd..b87076d9e8 100644 --- a/routers/user/user.go +++ b/routers/user/user.go @@ -168,7 +168,7 @@ func Delete(ctx *middleware.Context) { } } - ctx.Render.HTML(200, "user/delete", ctx.Data) + ctx.Render.Redirect("/", 302) } const ( diff --git a/templates/user/publickey.tmpl b/templates/user/publickey.tmpl index b671f63fdb..3b2cc1128f 100644 --- a/templates/user/publickey.tmpl +++ b/templates/user/publickey.tmpl @@ -5,7 +5,7 @@

    Account Setting

    • Account Profile
    • -
    • Password
    • +
    • Password
    • Notifications
    • SSH Keys
    • Security
    • From f6e32b1b08a5eff2c0cbdfe827b40fdea9dc676e Mon Sep 17 00:00:00 2001 From: slene Date: Sun, 16 Mar 2014 21:07:50 +0800 Subject: [PATCH 10/12] finish delete ssh key and delete account. all with confirm. --- models/user.go | 12 +- public/css/gogs.css | 19 +-- public/js/app.js | 21 +-- public/js/bootstrap.min.js | 261 +++++++++++++++++++++++++++++++++- routers/user/user.go | 24 +++- templates/user/delete.tmpl | 41 ++++-- templates/user/publickey.tmpl | 8 +- 7 files changed, 334 insertions(+), 52 deletions(-) diff --git a/models/user.go b/models/user.go index 463d4a271f..4b5a110915 100644 --- a/models/user.go +++ b/models/user.go @@ -175,8 +175,8 @@ func DeleteUser(user *User) error { // EncodePasswd encodes password to safe format. func (user *User) EncodePasswd() error { - newPasswd, err := scrypt.Key([]byte(user.Passwd), []byte(UserPasswdSalt), 16384, 8, 1, 64) - user.Passwd = fmt.Sprintf("%x", newPasswd) + var err error + user.Passwd, err = EncodePasswd(user.Passwd) return err } @@ -184,6 +184,14 @@ func UserPath(userName string) string { return filepath.Join(RepoRootPath, userName) } +func EncodePasswd(rawPasswd string) (string, error) { + newPasswd, err := scrypt.Key([]byte(rawPasswd), []byte(UserPasswdSalt), 16384, 8, 1, 64) + if err != nil { + return "", err + } + return fmt.Sprintf("%x", newPasswd), nil +} + func GetUserByKeyId(keyId int64) (*User, error) { user := new(User) has, err := orm.Sql("select a.* from user as a, public_key as b where a.id = b.owner_id and b.id=?", keyId).Get(user) diff --git a/public/css/gogs.css b/public/css/gogs.css index 6b510364b7..2adbde23e2 100755 --- a/public/css/gogs.css +++ b/public/css/gogs.css @@ -302,32 +302,23 @@ html, body { /* gogits user ssh keys */ #gogs-ssh-keys .list-group-item { - line-height: 48px; + padding: 15px 0; border-bottom: 1px solid #DDD; } +#gogs-ssh-keys .list-group-item .delete { + margin: -5px 50px 0; +} + #gogs-ssh-keys .list-group-item:after { clear: both; } -#gogs-ssh-keys .list-group-item:hover a.delete { - display: block; -} - #gogs-ssh-keys .name { font-size: 14px; font-weight: bold; } -#gogs-ssh-keys .list-group-item a.delete { - float: right; - color: white; - cursor: pointer; - margin-top: 10px; - border-radius: 3px; - display: none; -} - #gogs-ssh-keys .print { padding-left: 1em; color: #888; diff --git a/public/js/app.js b/public/js/app.js index 0f0ecc43ea..4a5d205347 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -99,15 +99,16 @@ function initRegister() { } function initUserSetting(){ - $('#gogs-ssh-keys').on("click",".delete",function(){ - var $this = $(this); - Gogits.ajaxDelete("",{"id":$this.data("del")},function(json){ - if(json.ok){ - window.location.reload(); - }else{ - alert(json.err); - } - }); - return false; + $('#gogs-ssh-keys .delete').confirmation({ + singleton: true, + onConfirm: function(e, $this){ + Gogits.ajaxDelete("",{"id":$this.data("del")},function(json){ + if(json.ok){ + window.location.reload(); + }else{ + alert(json.err); + } + }); + } }); } \ No newline at end of file diff --git a/public/js/bootstrap.min.js b/public/js/bootstrap.min.js index 1d4a4ed370..39beec4662 100755 --- a/public/js/bootstrap.min.js +++ b/public/js/bootstrap.min.js @@ -3,4 +3,263 @@ * Copyright 2011-2014 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ -if("undefined"==typeof jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.isLoading=!1};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",f.resetText||d.data("resetText",d[e]()),d[e](f[b]||this.options[b]),setTimeout(a.proxy(function(){"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},b.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});return this.$element.trigger(j),j.isDefaultPrevented()?void 0:(this.sliding=!0,f&&this.pause(),this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid.bs.carousel",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid.bs.carousel")},0)}).emulateTransitionEnd(1e3*d.css("transition-duration").slice(0,-1))):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid.bs.carousel")),f&&this.cycle(),this)};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("collapse in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?void this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);!e&&f.toggle&&"show"==c&&(c=!c),e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(jQuery),+function(a){"use strict";function b(b){a(d).remove(),a(e).each(function(){var d=c(a(this)),e={relatedTarget:this};d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown",e)),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown",e))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;(e||"destroy"!=c)&&(e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]())})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(a(c).is("body")?window:c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);{var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})}},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);if(g&&b<=e[0])return g!=(a=f[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parentsUntil(this.options.target,".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=this.pinnedOffset=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(b.RESET).addClass("affix");var a=this.$window.scrollTop(),c=this.$element.offset();return this.pinnedOffset=c.top-a},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"top"==this.affixed&&(e.top+=d),"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top(this.$element)),"function"==typeof h&&(h=f.bottom(this.$element));var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;if(this.affixed!==i){this.unpin&&this.$element.css("top","");var j="affix"+(i?"-"+i:""),k=a.Event(j+".bs.affix");this.$element.trigger(k),k.isDefaultPrevented()||(this.affixed=i,this.unpin="bottom"==i?this.getPinnedOffset():null,this.$element.removeClass(b.RESET).addClass(j).trigger(a.Event(j.replace("affix","affixed"))),"bottom"==i&&this.$element.offset({top:c-h-this.$element.height()}))}}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(jQuery); \ No newline at end of file +if("undefined"==typeof jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.isLoading=!1};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",f.resetText||d.data("resetText",d[e]()),d[e](f[b]||this.options[b]),setTimeout(a.proxy(function(){"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},b.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});return this.$element.trigger(j),j.isDefaultPrevented()?void 0:(this.sliding=!0,f&&this.pause(),this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid.bs.carousel",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid.bs.carousel")},0)}).emulateTransitionEnd(1e3*d.css("transition-duration").slice(0,-1))):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid.bs.carousel")),f&&this.cycle(),this)};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("collapse in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?void this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);!e&&f.toggle&&"show"==c&&(c=!c),e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(jQuery),+function(a){"use strict";function b(b){a(d).remove(),a(e).each(function(){var d=c(a(this)),e={relatedTarget:this};d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown",e)),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown",e))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;(e||"destroy"!=c)&&(e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]())})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(a(c).is("body")?window:c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);{var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})}},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);if(g&&b<=e[0])return g!=(a=f[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parentsUntil(this.options.target,".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=this.pinnedOffset=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(b.RESET).addClass("affix");var a=this.$window.scrollTop(),c=this.$element.offset();return this.pinnedOffset=c.top-a},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"top"==this.affixed&&(e.top+=d),"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top(this.$element)),"function"==typeof h&&(h=f.bottom(this.$element));var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;if(this.affixed!==i){this.unpin&&this.$element.css("top","");var j="affix"+(i?"-"+i:""),k=a.Event(j+".bs.affix");this.$element.trigger(k),k.isDefaultPrevented()||(this.affixed=i,this.unpin="bottom"==i?this.getPinnedOffset():null,this.$element.removeClass(b.RESET).addClass(j).trigger(a.Event(j.replace("affix","affixed"))),"bottom"==i&&this.$element.offset({top:c-h-this.$element.height()}))}}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(jQuery); + +/* =========================================================== + * forked from bootstrap-confirmation.js + * http://ethaizone.github.io/Bootstrap-Confirmation/ + * =========================================================== + * Copyright 2013 Nimit Suwannagate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * =========================================================== */ +!function ($) { + 'use strict'; + + //var for check event at body can have only one. + var event_body = false; + + // CONFIRMATION PUBLIC CLASS DEFINITION + // =============================== + var Confirmation = function (element, options) { + var that = this; + + this.init('confirmation', element, options); + + + $(element).on('show.bs.confirmation', function(e) { + that.options.onShow(e, this); + + $(this).addClass('open'); + + var options = that.options; + var all = options.all_selector; + + if(options.singleton) { + $(all+'.in').not(that.$element).confirmation('hide'); + } + }); + + $(element).on('hide.bs.confirmation', function(e) { + that.options.onHide(e, this); + + $(this).removeClass('open'); + }); + + $(element).on('shown.bs.confirmation', function(e) { + var options = that.options; + var all = options.all_selector; + + that.$element.on('click.dismiss.bs.confirmation', '[data-dismiss="confirmation"]', $.proxy(that.hide, that)); + + if(that.isPopout()) { + if(!event_body) { + event_body = $('body').on('click', function (e) { + if(that.$element.is(e.target)) return; + if(that.$element.has(e.target).length) return; + if($('.popover').has(e.target).length) return; + + that.$element.confirmation('hide'); + + $('body').unbind(e); + + event_body = false; + + return; + }); + } + } + }); + + $(element).on('click', function(e) { + e.preventDefault(); + }); + } + + if (!$.fn.popover || !$.fn.tooltip) throw new Error('Confirmation requires popover.js and tooltip.js'); + + Confirmation.DEFAULTS = $.extend({}, $.fn.popover.Constructor.DEFAULTS, { + placement : 'top', + title : 'Are you sure?', + btnOkClass : 'btn btn-danger btn-sm', + btnOkLabel : 'Yes', + btnOkIcon : '', + btnCancelClass : 'btn btn-default btn-sm', + btnCancelLabel : 'Cancel', + btnCancelIcon : '', + href : '#', + target : '_self', + singleton : true, + popout : true, + onShow : function(event, element){}, + onHide : function(event, element){}, + onConfirm : function(event, element){}, + onCancel : function(event, element){}, + template : '
      ' + + '

      ' + + '
      ' + + '
      Yes' + + ' No
      ' + + '
      ' + + '
      ' + }); + + + // NOTE: CONFIRMATION EXTENDS popover.js + // ================================ + Confirmation.prototype = $.extend({}, $.fn.popover.Constructor.prototype); + + Confirmation.prototype.constructor = Confirmation; + + Confirmation.prototype.getDefaults = function () { + return Confirmation.DEFAULTS; + } + + Confirmation.prototype.setContent = function () { + var that = this; + var $tip = this.tip(); + var title = this.getTitle(); + var $btnOk = $tip.find('[data-apply="confirmation"]'); + var $btnCancel = $tip.find('[data-dismiss="confirmation"]'); + var options = this.options + + $btnOk.addClass(this.getBtnOkClass()) + .html(this.getBtnOkLabel()) + .prepend($('').addClass(this.getBtnOkIcon()), " ") + .attr('href', this.getHref()) + .attr('target', this.getTarget()) + .off('click').on('click', function(event) { + options.onConfirm(event, that.$element); + + that.$element.confirmation('hide'); + }); + + $btnCancel.addClass(this.getBtnCancelClass()) + .html(this.getBtnCancelLabel()) + .prepend($('').addClass(this.getBtnCancelIcon()), " ") + .off('click').on('click', function(event){ + options.onCancel(event, that.$element); + + that.$element.confirmation('hide'); + }); + + $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title); + + $tip.removeClass('fade top bottom left right in'); + + // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do + // this manually by checking the contents. + if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide(); + } + + Confirmation.prototype.getBtnOkClass = function () { + var $e = this.$element; + var o = this.options; + + return $e.attr('data-btnOkClass') || (typeof o.btnOkClass == 'function' ? o.btnOkClass.call($e[0]) : o.btnOkClass); + } + + Confirmation.prototype.getBtnOkLabel = function () { + var $e = this.$element; + var o = this.options; + + return $e.attr('data-btnOkLabel') || (typeof o.btnOkLabel == 'function' ? o.btnOkLabel.call($e[0]) : o.btnOkLabel); + } + + Confirmation.prototype.getBtnOkIcon = function () { + var $e = this.$element; + var o = this.options; + + return $e.attr('data-btnOkIcon') || (typeof o.btnOkIcon == 'function' ? o.btnOkIcon.call($e[0]) : o.btnOkIcon); + } + + Confirmation.prototype.getBtnCancelClass = function () { + var $e = this.$element; + var o = this.options; + + return $e.attr('data-btnCancelClass') || (typeof o.btnCancelClass == 'function' ? o.btnCancelClass.call($e[0]) : o.btnCancelClass); + } + + Confirmation.prototype.getBtnCancelLabel = function () { + var $e = this.$element; + var o = this.options; + + return $e.attr('data-btnCancelLabel') || (typeof o.btnCancelLabel == 'function' ? o.btnCancelLabel.call($e[0]) : o.btnCancelLabel); + } + + Confirmation.prototype.getBtnCancelIcon = function () { + var $e = this.$element; + var o = this.options; + + return $e.attr('data-btnCancelIcon') || (typeof o.btnCancelIcon == 'function' ? o.btnCancelIcon.call($e[0]) : o.btnCancelIcon); + } + + Confirmation.prototype.getHref = function () { + var $e = this.$element; + var o = this.options; + + return $e.attr('data-href') || (typeof o.href == 'function' ? o.href.call($e[0]) : o.href); + } + + Confirmation.prototype.getTarget = function () { + var $e = this.$element; + var o = this.options; + + return $e.attr('data-target') || (typeof o.target == 'function' ? o.target.call($e[0]) : o.target); + } + + Confirmation.prototype.isPopout = function () { + var popout; + var $e = this.$element; + var o = this.options; + + popout = $e.attr('data-popout') || (typeof o.popout == 'function' ? o.popout.call($e[0]) : o.popout); + + if(popout == 'false') popout = false; + + return popout + } + + + // CONFIRMATION PLUGIN DEFINITION + // ========================= + var old = $.fn.confirmation; + + $.fn.confirmation = function (option) { + var that = this; + + return this.each(function () { + var $this = $(this); + var data = $this.data('bs.confirmation'); + var options = typeof option == 'object' && option; + + options = options || {}; + options.all_selector = that.selector; + + if (!data && option == 'destroy') return; + if (!data) $this.data('bs.confirmation', (data = new Confirmation(this, options))); + if (typeof option == 'string') data[option](); + }); + } + + $.fn.confirmation.Constructor = Confirmation + + + // CONFIRMATION NO CONFLICT + // =================== + $.fn.confirmation.noConflict = function () { + $.fn.confirmation = old; + + return this; + } +}(jQuery); \ No newline at end of file diff --git a/routers/user/user.go b/routers/user/user.go index b87076d9e8..ad84ff6c79 100644 --- a/routers/user/user.go +++ b/routers/user/user.go @@ -157,18 +157,28 @@ func Delete(ctx *middleware.Context) { return } - if err := models.DeleteUser(ctx.User); err != nil { + rawPasswd := ctx.Query("password") + encodedPwd, _ := models.EncodePasswd(rawPasswd) + if len(encodedPwd) == 0 || encodedPwd != ctx.User.Passwd { ctx.Data["HasError"] = true - switch err.Error() { - case models.ErrUserOwnRepos.Error(): - ctx.Data["ErrorMsg"] = "Your account still have ownership of repository, you have to delete or transfer them first." - default: - ctx.Handle(200, "user.Delete", err) + ctx.Data["ErrorMsg"] = "Your password error. Make sure you are owner of this account." + } else { + if err := models.DeleteUser(ctx.User); err != nil { + ctx.Data["HasError"] = true + switch err { + case models.ErrUserOwnRepos: + ctx.Data["ErrorMsg"] = "Your account still have ownership of repository, you have to delete or transfer them first." + default: + ctx.Handle(200, "user.Delete", err) + return + } + } else { + ctx.Render.Redirect("/") return } } - ctx.Render.Redirect("/", 302) + ctx.Render.HTML(200, "user/delete", ctx.Data) } const ( diff --git a/templates/user/delete.tmpl b/templates/user/delete.tmpl index 904201772e..a116787c80 100644 --- a/templates/user/delete.tmpl +++ b/templates/user/delete.tmpl @@ -13,22 +13,33 @@
  • -
    -

    Delete Account

    -

    {{if not .HasError}}The operation will delete your account permanently. Sorry to see you go, but we know you'll back soon.{{else}}{{.ErrorMsg}}{{end}}

    -
    -
    - +

    Delete Account

    +

    {{if not .HasError}}The operation will delete your account permanently. Sorry to see you go, but we know you'll back soon.{{else}}{{.ErrorMsg}}{{end}}

    +
    + +
    +
    +
    - {{template "base/footer" .}} \ No newline at end of file diff --git a/templates/user/publickey.tmpl b/templates/user/publickey.tmpl index 3b2cc1128f..104575d488 100644 --- a/templates/user/publickey.tmpl +++ b/templates/user/publickey.tmpl @@ -18,12 +18,14 @@

    SSH Keys

    {{if .AddSSHKeySuccess}}

    New SSH Key has been added !

    {{else if .HasError}}

    {{.ErrorMsg}}

    {{end}}