From 99e9b579baa41306f3a72ad6cd79ba1b951ca7a9 Mon Sep 17 00:00:00 2001 From: cxfksword Date: Sun, 14 Sep 2014 03:55:52 +0800 Subject: [PATCH] Add user login & setting --- auth.go | 77 +++++++++++++++++------------ db.go | 6 ++- deploy.go | 12 ++--- main.go | 42 ++++++---------- public/css/site.css | 68 +++++++++++++++++++++++++- public/js/site.js | 2 +- server.go | 2 +- system.go | 6 +-- templates/config.tmpl | 28 +++++------ templates/layout.tmpl | 13 +++-- templates/login.tmpl | 34 +++++++++++++ templates/setting.tmpl | 68 ++++++++++++++++++++++++++ templates/user.tmpl | 74 +++++++++++++++++++++++++--- user.go | 107 +++++++++++++++++++++++++++++++++++++++++ 14 files changed, 442 insertions(+), 97 deletions(-) create mode 100644 templates/login.tmpl create mode 100644 templates/setting.tmpl create mode 100644 user.go diff --git a/auth.go b/auth.go index b0c4bac..ca50eb8 100644 --- a/auth.go +++ b/auth.go @@ -1,42 +1,57 @@ package main import ( - "encoding/base64" - "github.com/go-martini/martini" "net/http" "strings" + + "github.com/go-martini/martini" + "github.com/martini-contrib/render" + webSessions "github.com/martini-contrib/sessions" ) -type AuthUser string - -var BasicRealm = "Authorization Required" - -func BasicFunc(authfn func(string, string) bool) martini.Handler { - return func(res http.ResponseWriter, req *http.Request, c martini.Context) { - if strings.HasPrefix(req.RequestURI, "/api") { - return - } - - auth := req.Header.Get("Authorization") - if len(auth) < 6 || auth[:6] != "Basic " { - unauthorized(res) - return - } - b, err := base64.StdEncoding.DecodeString(auth[6:]) - if err != nil { - unauthorized(res) - return - } - tokens := strings.SplitN(string(b), ":", 2) - if len(tokens) != 2 || !authfn(tokens[0], tokens[1]) { - unauthorized(res) - return - } - c.Map(AuthUser(tokens[0])) +func AuthFunc(req *http.Request, session webSessions.Session, r render.Render, c martini.Context) bool { + if strings.HasPrefix(req.RequestURI, "/api") || strings.HasPrefix(req.RequestURI, "/login") { + return true + } + + user := session.Get("auth_user") + if user == nil { + r.Redirect("/login", 302) + return true + } + + session.Set("auth_user", user) + c.Map(user.(string)) + return true + +} +func Login(params martini.Params, r render.Render) { + data := map[string]interface{}{"username": "", "msg": ""} + r.HTML(200, "login", data) +} +func Signin(req *http.Request, session webSessions.Session, r render.Render) { + req.ParseForm() + username := req.PostForm.Get("name") + password := req.PostForm.Get("password") + + var user User + db.First(&user, User{Name: username}) + if user.Id <= 0 { + data := map[string]interface{}{"username": "", "msg": "User not found!"} + r.HTML(200, "login", data) + return + } + + if password == user.Password { + session.Set("auth_user", username) + r.Redirect("/", 302) + } else { + data := map[string]interface{}{"username": "", "msg": "Incorrent password."} + r.HTML(200, "login", data) } } +func Signout(session webSessions.Session, r render.Render) { + session.Delete("auth_user") -func unauthorized(res http.ResponseWriter) { - res.Header().Set("WWW-Authenticate", "Basic realm=\""+BasicRealm+"\"") - http.Error(res, "Not Authorized", http.StatusUnauthorized) + r.Redirect("/login", 302) } diff --git a/db.go b/db.go index d6da913..5232dcc 100644 --- a/db.go +++ b/db.go @@ -27,8 +27,8 @@ type Server struct { type SystemConfig struct { Id int `gorm:"primary_key:yes" form:"id"` Name string `sql:"not null;unique" form:"name"` - EnableDevStage bool `form:"dev-stage"` - EnableProdStage bool `form:"prod-stage"` + EnableDevStage bool `sql:"not null" form:"dev-stage"` + EnableProdStage bool `sql:"not null" form:"prod-stage"` Way string `form:"way"` Path string `form:"path"` Shared string `form:"shared"` @@ -67,6 +67,7 @@ type User struct { Password string `form:"password"` Avatar string `form:"avatar"` Email string `form:"email"` + IsAdmin bool `sql:"not null" form:"isadmin"` CreatedAt time.Time } @@ -91,6 +92,7 @@ func InitDb() error { db.Save(User{ Name: "admin", Password: "123", + IsAdmin: true, CreatedAt: time.Now(), }) } diff --git a/deploy.go b/deploy.go index 7ab43b8..3544787 100644 --- a/deploy.go +++ b/deploy.go @@ -11,21 +11,21 @@ import ( ) // 默认发布 -func ExecuteDeployDefault(username AuthUser, params martini.Params, r render.Render) { +func ExecuteDeployDefault(username string, params martini.Params, r render.Render) { executeDeploy("", username, params, r) } // 发布到开发场景(Dev) -func ExecuteDeployDev(username AuthUser, params martini.Params, r render.Render) { +func ExecuteDeployDev(username string, params martini.Params, r render.Render) { executeDeploy("dev", username, params, r) } // 发布到产品场景(Prod) -func ExecuteDeployProd(username AuthUser, params martini.Params, r render.Render) { +func ExecuteDeployProd(username string, params martini.Params, r render.Render) { executeDeploy("prod", username, params, r) } -func executeDeploy(stage string, username AuthUser, params martini.Params, r render.Render) { +func executeDeploy(stage string, username string, params martini.Params, r render.Render) { id, _ := strconv.Atoi(params["id"]) var conf SystemConfig @@ -196,7 +196,7 @@ func executeDeploy(stage string, username AuthUser, params martini.Params, r ren } -func executeDeployUpdate(stage string, username AuthUser, params martini.Params, r render.Render) { +func executeDeployUpdate(stage string, username string, params martini.Params, r render.Render) { id, _ := strconv.Atoi(params["id"]) if isDeploying(id) { @@ -345,7 +345,7 @@ func executeDeployUpdate(stage string, username AuthUser, params martini.Params, } // 回滚部署 -func ExecuteRollback(username AuthUser, params martini.Params, r render.Render) { +func ExecuteRollback(username string, params martini.Params, r render.Render) { deployId, _ := strconv.Atoi(params["id"]) var deploy Deploy diff --git a/main.go b/main.go index 2f5b60c..2e25dc0 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,7 @@ import ( "github.com/go-martini/martini" "github.com/martini-contrib/binding" "github.com/martini-contrib/render" + webSessions "github.com/martini-contrib/sessions" ) type ActionMessage struct { @@ -35,15 +36,8 @@ func main() { m := martini.Classic() //m.Use(martini.Static("public", martini.StaticOptions{Prefix: "/public"})) - m.Use(BasicFunc(func(username string, password string) bool { - var user User - db.First(&user, User{Name: username}) - if user.Id <= 0 { - return false - } - - return username == user.Name && password == user.Password - })) + store := webSessions.NewCookieStore([]byte("secret_champloo")) + m.Use(webSessions.Sessions("champloo_session", store)) m.Use(render.Renderer(render.Options{ Layout: "layout", Funcs: []template.FuncMap{ @@ -79,8 +73,9 @@ func main() { }, }, })) + m.Use(AuthFunc) - m.Get("/", func(username AuthUser, r render.Render) { + m.Get("/", func(username string, r render.Render) { var confs []SystemConfig db.Order("id desc").Find(&confs) @@ -102,23 +97,16 @@ func main() { data := map[string]interface{}{"username": username, "confs": confs} r.HTML(200, "index", data) }) - m.Get("/users", func(username AuthUser, r render.Render) { - var users []User - db.Order("id desc").Find(&users) - - data := map[string]interface{}{"username": username, "users": users} - r.HTML(200, "user", data) - }) - m.Post("/users", binding.Bind(User{}), func(user User, r render.Render) { - user.CreatedAt = time.Now() - err := db.Save(&user).Error - if user.Id > 0 { - sendSuccessMsg(r, "") - } else { - sendFailMsg(r, "保存失败."+err.Error(), "") - } - }) - m.Get("/build/:id", func(username AuthUser, params martini.Params, r render.Render) { + m.Get("/login", Login) + m.Post("/login", Signin) + m.Get("/signout", Signout) + m.Get("/users", GetUsers) + m.Post("/users", binding.Bind(User{}), EditUsers) + m.Delete("/users/:id", DeleteUser) + m.Get("/setting", UserSetting) + m.Put("/users/:id/admin/:action", ToggleSetAdmin) + + m.Get("/build/:id", func(username string, params martini.Params, r render.Render) { id, _ := strconv.Atoi(params["id"]) var conf SystemConfig diff --git a/public/css/site.css b/public/css/site.css index d7e4e2e..013179f 100644 --- a/public/css/site.css +++ b/public/css/site.css @@ -18,9 +18,10 @@ margin-right: 20px; .profile { padding: 5px 0px 5px 20px; } -.profile a { +.profile a:link, .profile a:hover,.profile a:visited, .profile a:focus{ text-decoration: none; overflow: hidden; + border: 0px; } .profile .dropdown-menu { background: white; @@ -37,7 +38,7 @@ border-radius: 2px; -webkit-box-shadow: 0 2px 4px rgba(180,180,180,0.1); -moz-box-shadow: 0 2px 4px rgba(180,180,180,0.1); box-shadow: 0 2px 4px rgba(180,180,180,0.1); -overflow: hidden; +/*overflow: hidden;*/ } .profile .dropdown-menu>li { border: 0px !important; @@ -46,6 +47,32 @@ overflow: hidden; background-color: white !important; color: #555; } +.action-list>li { + padding: 0px; +} +.action-list>li>a { + padding: 10px 0px 10px 10px; + +} + +.action-list>li>a:hover { +background-color: rgba(128,128,128,0.5) !important; +} + +.action-list:before { +display: inline-block; +position: absolute; +top: -5px; +right: 14px; +width: 0; +height: 0; +vertical-align: top; +content: ""; +border-bottom: 5px solid white; +border-left: 5px solid transparent; +border-right: 5px solid transparent; +z-index: 999; +} .arrow { font-weight: normal !important; font-style: normal; @@ -260,6 +287,11 @@ border-radius: 3px; color: #666; font-size: 11px; } +div.tip { + margin-left: 450px; +margin-top : -25px; +position: absolute; +} .shell { list-style: none; @@ -443,6 +475,38 @@ color: #e65b98; margin-right: 5px; } + +.auth-form-header { +position: relative; +padding: 10px 20px; +margin: 0; +color: #fff; +text-shadow: 0 -1px 0 rgba(0,0,0,0.3); +background-color: #565656; +border: 1px solid #768995; +border-radius: 3px 3px 0 0; +} +.auth-form-header h1 { +margin-top: 0; +margin-bottom: 0; +font-size: 16px; +} + +.auth-form { +width: 400px; +margin: 60px auto; +} + +.auth-form-body { +padding: 20px; +font-size: 14px; +background-color: #fff; +border: 1px solid #d8dee2; +border-top: 0; +border-radius: 0 0 3px 3px; +} + + footer { position: fixed; bottom: 0; diff --git a/public/js/site.js b/public/js/site.js index 737cac8..8cc5317 100644 --- a/public/js/site.js +++ b/public/js/site.js @@ -34,7 +34,7 @@ function notify_error (msg) { text: msg, animate_speed: 'fast', stack: false, - delay: 1000, + delay: 2000, hide: true, type: 'error', width: "150px", diff --git a/server.go b/server.go index f9ee1b4..4fa928d 100644 --- a/server.go +++ b/server.go @@ -8,7 +8,7 @@ import ( "github.com/martini-contrib/render" ) -func GetServers(username AuthUser, r render.Render) { +func GetServers(username string, r render.Render) { var servers []Server db.Find(&servers) diff --git a/system.go b/system.go index 0577434..fae29de 100644 --- a/system.go +++ b/system.go @@ -9,7 +9,7 @@ import ( "github.com/martini-contrib/render" ) -func NewSystem(username AuthUser, r render.Render) { +func NewSystem(username string, r render.Render) { var servers []Server db.Select("tags").Find(&servers) @@ -100,7 +100,7 @@ func SaveSystem(req *http.Request, params martini.Params, r render.Render) { }) } -func GetSystemById(username AuthUser, params martini.Params, r render.Render) { +func GetSystemById(username string, params martini.Params, r render.Render) { id, _ := strconv.Atoi(params["id"]) var servers []Server @@ -135,7 +135,7 @@ func GetSystemById(username AuthUser, params martini.Params, r render.Render) { r.HTML(200, "config", data) } -func ToggleStarSystem(username AuthUser, params martini.Params, r render.Render) { +func ToggleStarSystem(username string, params martini.Params, r render.Render) { id, _ := strconv.Atoi(params["id"]) var user User diff --git a/templates/config.tmpl b/templates/config.tmpl index 537d735..5e3bb3e 100644 --- a/templates/config.tmpl +++ b/templates/config.tmpl @@ -49,29 +49,29 @@ - 选择场景后,发布的目录结构会改变 +
选择场景后,发布的目录结构会改变
- checkout:每次checkout一份新代码,copy:复制旧的代码再checkout,update:在原部署目录直接update +
checkout:每次checkout一份新代码,copy:复制旧的代码再checkout,update:在原部署目录直接update
- 请填写绝对路径,如/data/wwwroot/example/ +
请填写绝对路径,如/data/wwwroot/example/
- 变量$path代表当前部署目录,如$path/App/tpl/ +
变量$path代表当前部署目录,如$path/App/tpl/
- 超出指定数目的旧版本将会被删除 +
超出指定数目的旧版本将会被删除
@@ -81,17 +81,17 @@
- Git 或 SVN 源代码http获取路径地址,请确保服务器已正确安装的Git或SVN程序 +
Git 或 SVN 源代码http获取路径地址,请确保服务器已正确安装的Git或SVN程序
- SVN版本库登录帐号,Git不需要 +
SVN版本库登录帐号,Git不需要
- SVN版本库登录密码,Git不需要 +
SVN版本库登录密码,Git不需要
@@ -120,8 +120,8 @@

部署上线前执行命令

- -
每行一条命令,命令会在当前部署版本目录下执行. $path: 当前部署目录, $share: 共享目录, $release: 部署目录
+
+ 每行一条命令,命令会在当前部署版本目录下执行. $path: 当前部署目录, $share: 共享目录, $release: 部署目录
@@ -143,8 +143,8 @@

开发环境 - 部署上线前执行命令

- -
每行一条命令,命令会在当前部署版本目录下执行. $path: 当前部署目录, $share: 共享目录, $release: 部署目录
+
+ 每行一条命令,命令会在当前部署版本目录下执行. $path: 当前部署目录, $share: 共享目录, $release: 部署目录
@@ -165,8 +165,8 @@

产品环境 - 部署前执行命令

- -
每行一条命令,命令会在当前部署版本目录下执行. $path: 当前部署目录, $share: 共享目录, $release: 部署目录
+
+ 每行一条命令,命令会在当前部署版本目录下执行. $path: 当前部署目录, $share: 共享目录, $release: 部署目录
diff --git a/templates/layout.tmpl b/templates/layout.tmpl index 88d7d2c..faa0952 100644 --- a/templates/layout.tmpl +++ b/templates/layout.tmpl @@ -32,21 +32,26 @@ diff --git a/templates/login.tmpl b/templates/login.tmpl new file mode 100644 index 0000000..bce0343 --- /dev/null +++ b/templates/login.tmpl @@ -0,0 +1,34 @@ + + + + {{if ne .msg "" }} + + {{end}} + +
+ +
+

登录系统

+
+
+
+
+ + +
+
+ + +
+ +
+
+
+ + \ No newline at end of file diff --git a/templates/setting.tmpl b/templates/setting.tmpl new file mode 100644 index 0000000..e851691 --- /dev/null +++ b/templates/setting.tmpl @@ -0,0 +1,68 @@ + + + + + +
+
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+
+
+
+
+
+ + + diff --git a/templates/user.tmpl b/templates/user.tmpl index 2f4edc7..8fb92e2 100644 --- a/templates/user.tmpl +++ b/templates/user.tmpl @@ -33,7 +33,7 @@ - + @@ -41,9 +41,30 @@ - + + - + {{end}} @@ -59,8 +80,8 @@ notify_error('用户名不能为空'); return; } - if (! /[a-z]+/i.test($('input[name=name]').val() )) { - notify_error('用户名只能用小写英文字符'); + if (! /[a-z0-9]+/i.test($('input[name=name]').val() )) { + notify_error('用户名只能用小写英文字符和数字组成'); return; } $.post('/users', $(this).closest('form').serialize(), function(data) { @@ -68,9 +89,50 @@ notify('保存成功.'); location.href = location.href; } else { - alert(data.message); + notify_error(data.message); } }, 'JSON'); }); + $('.delete').on('click', function() { + if (!confirm('确认要删除吗?')) { + return false; + } + + $this = $(this); + var id = $this.attr('data-id'); + $.ajax({ + url: '/users/' + id, + type: 'DELETE', + dataType: 'JSON', + success: function(data){ + if (data.success) { + $tr = $this.closest('tr'); + $tr.css('background', 'red'); + $tr.fadeOut( "slow", function() { + $tr.remove(); + }); + } else { + notify_error('删除失败'); + } + } + }); + }); + + $('.ckadmin').on('click', function() { + $this = $(this); + var id = $this.attr('data-id'); + $.ajax({ + url: '/users/' + id + '/admin/' + (this.checked ? 'enable' : 'disable'), + type: 'PUT', + dataType: 'JSON', + success: function(data){ + if (!data.success) { + notify_error('设定失败'); + } + } + }); + }); + + }); diff --git a/user.go b/user.go new file mode 100644 index 0000000..6c45ff2 --- /dev/null +++ b/user.go @@ -0,0 +1,107 @@ +package main + +import ( + "net/http" + "strconv" + "strings" + "time" + + "github.com/go-martini/martini" + "github.com/martini-contrib/render" +) + +func GetUsers(username string, r render.Render) { + var users []User + db.Order("id desc").Find(&users) + + data := map[string]interface{}{"username": username, "users": users} + r.HTML(200, "user", data) +} +func EditUsers(req *http.Request, user User, r render.Render) { + req.ParseForm() + user.IsAdmin = req.PostForm.Get("isadmin") == "on" + if user.Name == "" || user.Password == "" { + sendFailMsg(r, "保存失败,用户名或密码不能为空.", "") + return + } + if user.Avatar != "" { + if !strings.HasSuffix(user.Avatar, ".jpg") && + !strings.HasSuffix(user.Avatar, ".jpeg") && + !strings.HasSuffix(user.Avatar, ".gif") && + !strings.HasSuffix(user.Avatar, ".png") { + sendFailMsg(r, "保存失败,头像只支持gif,jpg,png格式.", "") + return + } + } + user.CreatedAt = time.Now() + err := db.Save(&user).Error + if user.Id > 0 { + sendSuccessMsg(r, "") + } else { + sendFailMsg(r, "保存失败."+err.Error(), "") + } +} +func DeleteUser(params martini.Params, r render.Render) { + id, err := strconv.Atoi(params["id"]) + if err != nil { + r.JSON(200, ActionMessage{ + Success: false, + Message: "id参数错误", + }) + return + } + + err = db.Delete(&User{Id: id}).Error + if err != nil { + r.JSON(200, ActionMessage{ + Success: false, + Message: "删除出错." + err.Error(), + }) + return + } + r.JSON(200, ActionMessage{ + Success: true, + Message: "成功", + }) +} +func ToggleSetAdmin(params martini.Params, r render.Render) { + id, err := strconv.Atoi(params["id"]) + if err != nil { + r.JSON(200, ActionMessage{ + Success: false, + Message: "id参数错误", + }) + return + } + + action := params["action"] + var user User + if action == "enable" { + db.First(&user, id) + user.IsAdmin = true + err = db.Debug().Save(user).Error + } else { + db.First(&user, id) + user.IsAdmin = false + err = db.Save(user).Error + } + + if err != nil { + r.JSON(200, ActionMessage{ + Success: false, + Message: "保存出错." + err.Error(), + }) + return + } + r.JSON(200, ActionMessage{ + Success: true, + Message: "成功", + }) +} +func UserSetting(username string, r render.Render) { + var user User + db.First(&user, User{Name: username}) + + data := map[string]interface{}{"username": username, "user": user} + r.HTML(200, "setting", data) +}
序号用户名密码创建时间操作序号用户名邮箱管理员创建时间操作
{{ .Id }} {{ .Name }}  {{ .Name }}***{{ .Email }} + {{if ne .Name "admin"}} + {{if .IsAdmin }} + + {{else}} + + {{end}} + {{else}} + + {{end}} + {{ .CreatedAt | formatTime }} + {{if ne .Name "admin"}} + 删除 + {{end}} +