feat($user): 支持多用户, #12

#16
pull/21/merge
ouqiang 2017-10-01 21:49:46 +08:00
parent f0ff9a88a7
commit 3474636e23
19 changed files with 597 additions and 45 deletions

View File

@ -1,5 +1,8 @@
[![Build Status](https://travis-ci.org/ouqiang/gocron.png)](https://travis-ci.org/ouqiang/gocron)
# gocron - 定时任务管理系统
[![Build Status](https://travis-ci.org/ouqiang/gocron.png)](https://travis-ci.org/ouqiang/gocron)
[![Downloads](https://img.shields.io/github/downloads/ouqiang/gocron/total.svg)](https://github.com/ouqiang/gocron/releases)
[![license](https://img.shields.io/github/license/mashape/apistatus.svg?maxAge=2592000)](https://github.com/ouqiang/gocron/blob/master/LICENSE)
[![Release](https://img.shields.io/github/release/ouqiang/gocron.svg?label=Release)](https://github.com/ouqiang/gocron/releases)
# 项目简介
使用Go语言开发的定时任务集中调度和管理系统, 用于替代Linux-crontab [查看文档](https://github.com/ouqiang/gocron/wiki)
@ -33,7 +36,7 @@
## 下载
[v1.2.2](https://github.com/ouqiang/gocron/releases/tag/v1.2.2)
[releases](https://github.com/ouqiang/gocron/releases)
[版本升级](https://github.com/ouqiang/gocron/wiki/版本升级)
@ -84,9 +87,8 @@
- [x] 批量开启、关闭、删除任务
- [x] 调度器与任务节点通信支持https
- [x] 任务分组
- [ ] 多用户
- [x] 多用户
- [ ] 权限控制
- [ ] 新增任务API接口
## 程序使用的组件
* Web框架 [Macaron](http://go-macaron.com/)

View File

@ -16,6 +16,8 @@ OS=''
ARCH=''
# 应用名称
APP_NAME='gocron'
# 版本号
VERSION=''
# 可执行文件名
EXEC_NAME=''
# 压缩包名称
@ -23,13 +25,15 @@ COMPRESS_FILE=''
# -p 平台 -a 架构
while getopts "p:a:" OPT;
while getopts "p:a:v:" OPT;
do
case $OPT in
p) OS=$OPTARG
;;
a) ARCH=$OPTARG
;;
v) VERSION=$OPTARG
;;
esac
done
@ -52,6 +56,11 @@ if [[ $ARCH != '386' && $ARCH != 'amd64' ]];then
exit 1
fi
if [[ -z $VERSION ]];then
echo '版本号不能为空'
exit 1
fi
echo '开始编译调度器'
if [[ $OS = 'windows' ]];then
GOOS=$OS GOARCH=$ARCH go build -tags gocron -ldflags '-w'
@ -66,10 +75,10 @@ echo '编译完成'
if [[ $OS = 'windows' ]];then
EXEC_NAME=${APP_NAME}.exe
COMPRESS_FILE=${APP_NAME}-${OS}-${ARCH}.zip
COMPRESS_FILE=${APP_NAME}-v${VERSION}-${OS}-${ARCH}.zip
else
EXEC_NAME=${APP_NAME}
COMPRESS_FILE=${APP_NAME}-${OS}-${ARCH}.tar.gz
COMPRESS_FILE=${APP_NAME}-v${VERSION}-${OS}-${ARCH}.tar.gz
fi
mkdir -p $TEMP_DIR/$APP_NAME

View File

@ -14,6 +14,8 @@ OS=''
ARCH=''
# 应用名称
APP_NAME='gocron-node'
# 版本号
VERSION=''
# 可执行文件名
EXEC_NAME=''
# 压缩包名称
@ -21,13 +23,15 @@ COMPRESS_FILE=''
# -p 平台 -a 架构
while getopts "p:a:" OPT;
while getopts "p:a:v:" OPT;
do
case $OPT in
p) OS=$OPTARG
;;
a) ARCH=$OPTARG
;;
v) VERSION=$OPTARG
;;
esac
done
@ -50,12 +54,17 @@ if [[ $ARCH != '386' && $ARCH != 'amd64' ]];then
exit 1
fi
if [[ -z $VERSION ]];then
echo '版本号不能为空'
exit 1
fi
if [[ $OS = 'windows' ]];then
EXEC_NAME=${APP_NAME}.exe
COMPRESS_FILE=${APP_NAME}-${OS}-${ARCH}.zip
COMPRESS_FILE=${APP_NAME}-v${VERSION}-${OS}-${ARCH}.zip
else
EXEC_NAME=${APP_NAME}
COMPRESS_FILE=${APP_NAME}-${OS}-${ARCH}.tar.gz
COMPRESS_FILE=${APP_NAME}-v${VERSION}-${OS}-${ARCH}.tar.gz
fi
echo '开始编译任务节点'

View File

@ -10,7 +10,7 @@ import (
"github.com/ouqiang/gocron/cmd"
)
const AppVersion = "1.2.2"
const AppVersion = "1.3"
func main() {
app := cli.NewApp()

View File

@ -53,10 +53,11 @@ func (migration *Migration) Upgrade(oldVersionId int) {
return
}
versionIds := []int{110, 122}
versionIds := []int{110, 122, 130}
upgradeFuncs := []func(*xorm.Session) error{
migration.upgradeFor110,
migration.upgradeFor122,
migration.upgradeFor130,
}
startIndex := -1
@ -155,3 +156,16 @@ func (migration *Migration) upgradeFor122(session *xorm.Session) error {
return err
}
// 升级到1.2.3版本
func (migration *Migration) upgradeFor130(session *xorm.Session) error {
logger.Info("开始升级到v1.3")
tableName := TablePrefix + "user"
// 删除user表deleted字段
_, err := session.Exec(fmt.Sprintf("ALTER TABLE %s DROP COLUMN deleted", tableName))
logger.Info("已升级到v1.3\n")
return err
}

View File

@ -26,7 +26,6 @@ const (
Running Status = 1 // 运行中
Finish Status = 2 // 完成
Cancel Status = 3 // 取消
Waiting Status = 5 // 等待中
)
const (

View File

@ -16,7 +16,6 @@ type User struct {
Email string `xorm:"varchar(50) notnull unique default '' "` // 邮箱
Created time.Time `xorm:"datetime notnull created"`
Updated time.Time `xorm:"datetime updated"`
Deleted time.Time `xorm:"datetime deleted"`
IsAdmin int8 `xorm:"tinyint notnull default 0"` // 是否是管理员 1:管理员 0:普通用户
Status Status `xorm:"tinyint notnull default 1"` // 1: 正常 0:禁用
BaseModel `xorm:"-"`
@ -65,8 +64,8 @@ func (user *User) Enable(id int) (int64, error) {
// 验证用户名和密码
func (user *User) Match(username, password string) bool {
where := "(name = ? OR email = ?)"
_, err := Db.Where(where, username, username).Get(user)
where := "(name = ? OR email = ?) AND status =? "
_, err := Db.Where(where, username, username, Enabled).Get(user)
if err != nil {
return false
}
@ -78,19 +77,35 @@ func (user *User) Match(username, password string) bool {
return true
}
// 获取用户详情
func (user *User) Find(id int) (error) {
_, err := Db.Id(id).Get(user)
return err
}
// 用户名是否存在
func (user *User) UsernameExists(username string) (int64, error) {
func (user *User) UsernameExists(username string, uid int) (int64, error) {
if uid > 0 {
return Db.Where("name = ? AND id != ?", username, uid).Count(user)
}
return Db.Where("name = ?", username).Count(user)
}
// 邮箱地址是否存在
func (user *User) EmailExists(email string) (int64, error) {
func (user *User) EmailExists(email string, uid int) (int64, error) {
if uid > 0 {
return Db.Where("email = ? AND id != ?", email, uid).Count(user)
}
return Db.Where("email = ?", email).Count(user)
}
func (user *User) List() ([]User, error) {
func (user *User) List(params CommonMap) ([]User, error) {
user.parsePageAndPageSize(params)
list := make([]User, 0)
err := Db.Desc("id").Find(&list)
err := Db.Desc("id").Limit(user.PageSize, user.pageLimitOffset()).Find(&list)
return list, err
}

View File

@ -5,6 +5,7 @@ import (
"gopkg.in/macaron.v1"
)
// ParsePageAndPageSize 解析查询参数中的页数和每页数量
func ParsePageAndPageSize(ctx *macaron.Context, params models.CommonMap) {
page := ctx.QueryInt("page")
pageSize := ctx.QueryInt("page_size")

View File

@ -18,6 +18,7 @@ import (
"strings"
)
// Index 主机列表
func Index(ctx *macaron.Context) {
hostModel := new(models.Host)
queryParams := parseQueryParams(ctx)
@ -42,11 +43,13 @@ func Index(ctx *macaron.Context) {
ctx.HTML(200, "host/index")
}
// Create 创建主机页面
func Create(ctx *macaron.Context) {
ctx.Data["Title"] = "添加主机"
ctx.HTML(200, "host/host_form")
}
// Edit 修改主机页面
func Edit(ctx *macaron.Context) {
ctx.Data["Title"] = "编辑主机"
hostModel := new(models.Host)
@ -67,6 +70,7 @@ type HostForm struct {
Remark string
}
// Error 表单验证错误处理
func (f HostForm) Error(ctx *macaron.Context, errs binding.Errors) {
if len(errs) == 0 {
return
@ -77,6 +81,7 @@ func (f HostForm) Error(ctx *macaron.Context, errs binding.Errors) {
ctx.Resp.Write([]byte(content))
}
// Store 保存、修改主机信息
func Store(ctx *macaron.Context, form HostForm) string {
json := utils.JsonResponse{}
hostModel := new(models.Host)
@ -129,6 +134,7 @@ func Store(ctx *macaron.Context, form HostForm) string {
return json.Success("保存成功", nil)
}
// Remove 删除主机
func Remove(ctx *macaron.Context) string {
id, err := strconv.Atoi(ctx.Params(":id"))
json := utils.JsonResponse{}
@ -161,6 +167,7 @@ func Remove(ctx *macaron.Context) string {
return json.Success("操作成功", nil)
}
// Ping 测试主机是否可连接
func Ping(ctx *macaron.Context) string {
id := ctx.ParamsInt(":id")
hostModel := new(models.Host)

View File

@ -41,11 +41,20 @@ func Register(m *macaron.Macaron) {
// 用户
m.Group("/user", func() {
m.Get("", user.Index)
m.Get("/create", user.Create)
m.Get("/edit/:id", user.Edit)
m.Post("/store", binding.Bind(user.UserForm{}), user.Store)
m.Post("/remove/:id", user.Remove)
m.Get("/login", user.Login)
m.Post("/login", user.ValidateLogin)
m.Get("/logout", user.Logout)
m.Get("/editPassword", user.EditPassword)
m.Post("/editPassword", user.UpdatePassword)
m.Post("/enable/:id", user.Enable)
m.Post("/disable/:id", user.Disable)
m.Get("/editMyPassword", user.EditMyPassword)
m.Post("/editMyPassword", user.UpdateMyPassword)
m.Get("/editPassword/:id", user.EditPassword)
m.Post("/editPassword/:id", user.UpdatePassword)
})
// 定时任务

View File

@ -208,7 +208,7 @@ func Store(ctx *macaron.Context, form TaskForm) string {
taskHostModel.Remove(id)
}
status, err := taskModel.GetStatus(id)
status, _ := taskModel.GetStatus(id)
if status == models.Enabled && taskModel.Level == models.TaskLevelParent {
addTaskToTimer(id)
}

View File

@ -6,23 +6,222 @@ import (
"github.com/ouqiang/gocron/models"
"github.com/ouqiang/gocron/modules/logger"
"github.com/ouqiang/gocron/modules/utils"
"github.com/ouqiang/gocron/routers/base"
"gopkg.in/macaron.v1"
"github.com/Unknwon/paginater"
"html/template"
"fmt"
"strings"
)
// @author qiang.ou<qingqianludao@gmail.com>
// @date 2017/4/23-14:16
// UserForm 用户表单
type UserForm struct {
Id int
Name string `binding:"Required;MaxSize(32)"` // 用户名
Password string // 密码
ConfirmPassword string // 确认密码
Email string `binding:"Required;MaxSize(50)"` // 邮箱
IsAdmin int8 // 是否是管理员 1:管理员 0:普通用户
Status models.Status
}
// Index 用户列表页
func Index(ctx *macaron.Context) {
queryParams := parseQueryParams(ctx)
userModel := new(models.User)
users, err := userModel.List(queryParams)
if err != nil {
logger.Error(err)
}
total, err := userModel.Total()
if err != nil {
logger.Error(err)
}
PageParams := fmt.Sprintf("page_size=%d", queryParams["PageSize"])
queryParams["PageParams"] = template.URL(PageParams)
p := paginater.New(int(total), queryParams["PageSize"].(int), queryParams["Page"].(int), 5)
ctx.Data["Pagination"] = p
ctx.Data["Params"] = queryParams
ctx.Data["Title"] = "用户列表"
ctx.Data["Users"] = users
ctx.HTML(200, "user/index")
}
// 解析查询参数
func parseQueryParams(ctx *macaron.Context) models.CommonMap {
var params models.CommonMap = models.CommonMap{}
base.ParsePageAndPageSize(ctx, params)
return params
}
// Create 新增用户页
func Create(ctx *macaron.Context) {
userModel := new(models.User)
userModel.Status = models.Enabled
userModel.IsAdmin = 0
ctx.Data["User"] = userModel
ctx.Data["Title"] = "添加用户"
ctx.HTML(200, "user/user_form")
}
// 编辑页面
func Edit(ctx *macaron.Context) {
ctx.Data["Title"] = "编辑用户"
userModel := new(models.User)
id := ctx.ParamsInt(":id")
err := userModel.Find(id)
if err != nil {
logger.Error(err)
}
ctx.Data["User"] = userModel
ctx.HTML(200, "user/user_form")
}
// 保存任务
func Store(ctx *macaron.Context, form UserForm) string {
form.Name = strings.TrimSpace(form.Name)
form.Email = strings.TrimSpace(form.Email)
form.Password = strings.TrimSpace(form.Password)
form.ConfirmPassword = strings.TrimSpace(form.ConfirmPassword)
json := utils.JsonResponse{}
userModel := models.User{}
nameExists, err := userModel.UsernameExists(form.Name, form.Id)
if err != nil {
return json.CommonFailure(utils.FailureContent, err)
}
if nameExists > 0 {
return json.CommonFailure("用户名已存在")
}
emailExists, err := userModel.EmailExists(form.Email, form.Id)
if err != nil {
return json.CommonFailure(utils.FailureContent, err)
}
if emailExists > 0 {
return json.CommonFailure("邮箱已存在")
}
if form.Id == 0 {
if form.Password == "" {
return json.CommonFailure("请输入密码")
}
if form.ConfirmPassword == "" {
return json.CommonFailure("请再次输入密码")
}
if form.Password != form.ConfirmPassword {
return json.CommonFailure("两次密码输入不一致")
}
}
userModel.Name = form.Name
userModel.Email = form.Email
userModel.Password = form.Password
userModel.IsAdmin = form.IsAdmin
userModel.Status = form.Status
if form.Id == 0 {
_, err = userModel.Create()
if err != nil {
return json.CommonFailure("添加失败", err)
}
} else {
_, err = userModel.Update(form.Id, models.CommonMap{
"name": form.Name,
"email": form.Email,
"status": form.Status,
"is_admin": form.IsAdmin,
})
if err != nil {
return json.CommonFailure("修改失败", err)
}
}
return json.Success("保存成功", nil)
}
// 删除用户
func Remove(ctx *macaron.Context) string {
id := ctx.ParamsInt(":id")
json := utils.JsonResponse{}
userModel := new(models.User)
_, err := userModel.Delete(id)
if err != nil {
return json.CommonFailure(utils.FailureContent, err)
}
return json.Success(utils.SuccessContent, nil)
}
// 激活用户
func Enable(ctx *macaron.Context) string {
return changeStatus(ctx, models.Enabled)
}
// 禁用用户
func Disable(ctx *macaron.Context) string {
return changeStatus(ctx, models.Disabled)
}
// 改变任务状态
func changeStatus(ctx *macaron.Context, status models.Status) string {
id := ctx.ParamsInt(":id")
json := utils.JsonResponse{}
userModel := new(models.User)
_, err := userModel.Update(id, models.CommonMap{
"Status": status,
})
if err != nil {
return json.CommonFailure(utils.FailureContent, err)
}
return json.Success(utils.SuccessContent, nil)
}
// Login 用户登录
func Login(ctx *macaron.Context) {
ctx.Data["Title"] = "用户登录"
ctx.HTML(200, "user/login")
}
// EditPassword 修改密码页面
func EditPassword(ctx *macaron.Context) {
id := ctx.ParamsInt(":id")
ctx.Data["Title"] = "修改密码"
ctx.Data["Id"] = id
ctx.HTML(200, "user/editPassword")
}
func UpdatePassword(ctx *macaron.Context, sess session.Store) string {
// UpdatePassword 更新我的密码
func UpdatePassword(ctx *macaron.Context) string {
id := ctx.ParamsInt(":id")
newPassword := ctx.QueryTrim("new_password")
confirmNewPassword := ctx.QueryTrim("confirm_new_password")
json := utils.JsonResponse{}
if newPassword == "" || confirmNewPassword == "" {
return json.CommonFailure("请输入密码")
}
userModel := new(models.User)
_, err := userModel.UpdatePassword(id, newPassword)
if err != nil {
return json.CommonFailure("修改失败")
}
return json.Success("修改成功", nil)
}
// EditMyPassword 修改我的密码页面
func EditMyPassword(ctx *macaron.Context) {
ctx.Data["Title"] = "修改密码"
ctx.HTML(200, "user/editMyPassword")
}
// UpdateMyPassword 更新我的密码
func UpdateMyPassword(ctx *macaron.Context, sess session.Store) string {
oldPassword := ctx.QueryTrim("old_password")
newPassword := ctx.QueryTrim("new_password")
confirmNewPassword := ctx.QueryTrim("confirm_new_password")
@ -48,6 +247,7 @@ func UpdatePassword(ctx *macaron.Context, sess session.Store) string {
return json.Success("修改成功", nil)
}
// ValidateLogin 验证用户登录
func ValidateLogin(ctx *macaron.Context, sess session.Store, cpt *captcha.Captcha) string {
username := ctx.QueryTrim("username")
password := ctx.QueryTrim("password")
@ -78,6 +278,7 @@ func ValidateLogin(ctx *macaron.Context, sess session.Store, cpt *captcha.Captch
return json.Success("登录成功", nil)
}
// Logout 用户退出
func Logout(ctx *macaron.Context, sess session.Store) {
if IsLogin(sess) {
err := sess.Destory(ctx)
@ -89,6 +290,7 @@ func Logout(ctx *macaron.Context, sess session.Store) {
Login(ctx)
}
// Username 获取session中的用户名
func Username(sess session.Store) string {
username, ok := sess.Get("username").(string)
if ok {
@ -98,6 +300,7 @@ func Username(sess session.Store) string {
return ""
}
// Uid 获取session中的Uid
func Uid(sess session.Store) int {
uid, ok := sess.Get("uid").(int)
if ok {
@ -107,6 +310,7 @@ func Uid(sess session.Store) int {
return 0
}
// IsLogin 判断用户是否已登录
func IsLogin(sess session.Store) bool {
uid, ok := sess.Get("uid").(int)
if ok && uid > 0 {
@ -116,6 +320,7 @@ func IsLogin(sess session.Store) bool {
return false
}
// IsAdmin 判断当前用户是否是管理员
func IsAdmin(sess session.Store) bool {
isAdmin, ok := sess.Get("isAdmin").(int8)
if ok && isAdmin > 0 {

View File

@ -48,7 +48,7 @@
{{{.LoginUsername}}}
<i class="dropdown icon"></i>
<div class="menu">
<a href="/user/editPassword" class="item"><i class="edit icon"></i></a>
<a href="/user/editMyPassword" class="item"><i class="edit icon"></i></a>
<a class="item" href="/user/logout"><i class="sign out icon"></i>退</a>
</div>
{{{end}}}
@ -62,7 +62,7 @@
<div class="right menu">
<a class="item {{{if or (eq .Controller "task") (eq .Controller "delaytask")}}}active{{{end}}}" href="/task"><i class="tasks icon"></i></a>
<a class="item {{{if eq .Controller "host"}}}active{{{end}}}" href="/host"><i class="linux icon"></i></a>
<!-- <a class="item {{{if eq .Controller "user"}}}active{{{end}}}" href="/user"><i class="user icon"></i></a> -->
<a class="item {{{if eq .Controller "user"}}}active{{{end}}}" href="/user"><i class="user icon"></i></a>
{{{if gt .LoginUid 0}}}
<a class="item {{{if eq .Controller "manage"}}}active{{{end}}}" href="/manage/slack/edit"><i class="settings icon"></i></a>
{{{end}}}

View File

@ -46,7 +46,7 @@
</div>
<div class="ui primary submit button"></div>
<a class="ui button" onclick="location.href='/task';"></a>
<a class="ui button" onclick="location.href='/host';"></a>
</form>
</div>
</div>

View File

@ -0,0 +1,73 @@
{{{ template "common/header" . }}}
<div class="ui small modal">
<div class="header"></div>
<div class="content">
<form class="ui form">
<div class="two fields">
<div class="field">
<label></label>
<input class="small input" type="password" name="old_password" placeholder="原密码">
</div>
</div>
<div class="two fields">
<div class="field">
<label></label>
<input type="password" name="new_password" placeholder="新密码">
</div>
</div>
<div class="two fields">
<div class="field">
<label></label>
<input type="password" name="confirm_new_password" placeholder="确认新密码">
</div>
</div>
<button class="ui button primary button" ></button>
</form>
</div>
</div>
<script type="text/javascript">
$('.ui.modal').modal('setting', 'closable', false).modal('show');
$('.ui.form').form(
{
onSuccess: function(event, fields) {
util.post('/user/editMyPassword', fields, function(code, message) {
swal("操作成功", '', 'success');
location.href = "/task"
});
return false;
},
fields: {
oldPassword: {
identifier : 'old_password',
rules: [
{
type : 'empty',
prompt : ''
}
]
},
newPassword: {
identifier : 'new_password',
rules: [
{
type : 'empty',
prompt : ''
}
]
},
confirmNewPassword: {
identifier : 'confirm_new_password',
rules: [
{
type : 'match[new_password]',
prompt : ''
}
]
}
},
inline : true
});
</script>
{{{ template "common/footer" . }}}

View File

@ -3,12 +3,6 @@
<div class="header"></div>
<div class="content">
<form class="ui form">
<div class="two fields">
<div class="field">
<label></label>
<input class="small input" type="password" name="old_password" placeholder="原密码">
</div>
</div>
<div class="two fields">
<div class="field">
<label></label>
@ -21,7 +15,7 @@
<input type="password" name="confirm_new_password" placeholder="确认新密码">
</div>
</div>
<button class="ui button primary button" ></button>
<button class="ui button primary button" ></button>
</form>
</div>
</div>
@ -31,23 +25,14 @@
$('.ui.form').form(
{
onSuccess: function(event, fields) {
util.post('/user/editPassword', fields, function(code, message) {
util.post('/user/editPassword/{{{.Id}}}', fields, function(code, message) {
swal("操作成功", '', 'success');
location.href = "/task"
location.href = "/user"
});
return false;
},
fields: {
oldPassword: {
identifier : 'old_password',
rules: [
{
type : 'empty',
prompt : ''
}
]
},
newPassword: {
identifier : 'new_password',
rules: [

73
templates/user/index.html Normal file
View File

@ -0,0 +1,73 @@
{{{ template "common/header" . }}}
<div class="ui grid">
{{{ template "user/menu" . }}}
<div class="twelve wide column">
<div class="pageHeader">
<div class="segment">
<h3 class="ui dividing header">
<a href="/user/create">
<i class="large add icon"></i>
<div class="content">
</div>
</a>
</h3>
</div>
</div>
<table class="ui celled table">
<thead>
<tr>
<th>ID</th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{{{range $i, $v := .Users}}}
<tr>
<td>{{{.Id}}}</td>
<td>{{{.Name}}}</td>
<td>{{{.Email}}}</td>
<td>{{{if .IsAdmin}}}{{{else}}}{{{end}}}</td>
<td>{{{if .Status}}}{{{else}}}{{{end}}}</td>
<td class="operation">
<a class="ui purple button" href="/user/edit/{{{.Id}}}"></a>
{{{if eq .Status 1}}}
<button class="ui button red" onclick="changeStatus({{{.Id}}},{{{.Status}}})"></button>&nbsp;&nbsp;
{{{else}}}
<button class="ui button twitter" onclick="changeStatus({{{.Id}}},{{{.Status}}})"></button>&nbsp;&nbsp;
{{{end}}}
<a href="/user/editPassword/{{{.Id}}}">
<button class="ui button facebook"></button>
</a>
<button class="ui positive button" onclick="util.removeConfirm('/user/remove/{{{.Id}}}')"></button>
</td>
</tr>
{{{end}}}
</tbody>
</table>
{{{ template "common/pagination" .}}}
</div>
</div>
<script type="text/javascript">
function changeStatus(id ,status) {
var url = '';
if (status) {
url = '/user/disable';
} else {
url = '/user/enable';
}
url += '/' + id;
util.post(url,{}, function() {
location.reload();
});
}
</script>
{{{ template "common/footer" . }}}

9
templates/user/menu.html Normal file
View File

@ -0,0 +1,9 @@
<div class="four wide column">
<div class="verticalMenu">
<div class="ui vertical pointing menu fluid">
<a class="{{{if eq .URI "/user"}}}active teal{{{end}}} item" href="/user">
<i class="user icon"></i>
</a>
</div>
</div>
</div>

View File

@ -0,0 +1,142 @@
{{{ template "common/header" . }}}
<div class="ui grid">
{{{ template "user/menu" . }}}
<div class="twelve wide column">
<div class="pageHeader">
<div class="segment">
<h3 class="ui dividing header">
<div class="content">
</div>
</h3>
</div>
</div>
<form class="ui form fluid vertical segment">
<input type="hidden" name="id" value="{{{.User.Id}}}">
<div class="three fields">
<div class="field">
<label></label>
<div class="ui small input">
<input type="text" name="name" value="{{{.User.Name}}}" placeholder="请输入用户名">
</div>
</div>
<div class="field">
<label></label>
<div class="ui small input">
<input type="text" name="email" value="{{{.User.Email}}}" placeholder="请输入邮箱">
</div>
</div>
</div>
{{{if eq .User.Id 0}}}
<div class="three fields">
<div class="field">
<label></label>
<input type="password" name="password" placeholder="请输入密码">
</div>
<div class="field">
<label></label>
<input type="password" name="confirm_password" placeholder="请再次输入密码">
</div>
</div>
{{{end}}}
<div class="three fields">
<div class="field">
<label></label>
<input type="radio" name="is_admin" value="0" {{{if eq .User.IsAdmin 0}}}checked{{{end}}}>
<input type="radio" name="is_admin" value="1" {{{if eq .User.IsAdmin 1}}}checked{{{end}}}>
</div>
<div class="field">
<label></label>
<input type="radio" name="status" value="1" {{{if eq .User.Status 1}}}checked{{{end}}}>
<input type="radio" name="status" value="0"
{{{if eq .User.Status 0}}}checked{{{end}}}
>
</div>
</div>
<div class="ui primary submit button"></div>
<a class="ui button" onclick="location.href='/user';"></a>
</form>
</div>
</div>
<script type="text/javascript">
var $uiForm = $('.ui.form');
$($uiForm).form(
{
onSuccess: function(event, fields) {
util.post('/user/store', fields, function(code, message) {
location.href = "/user"
});
return false;
},
fields: {
name: {
identifier : 'name',
rules: [
{
type : 'empty',
prompt : ''
},
{
type : 'minLength[3]',
prompt : '3'
},
{
type : 'maxLength[32]',
prompt : '32'
}
]
},
password: {
identifier : 'password',
rules: [
{
type : 'empty',
prompt : ''
},
{
type : 'minLength[6]',
prompt : '6'
}
]
},
confirmPassword: {
identifier : 'confirm_password',
rules: [
{
type : 'empty',
prompt : ''
},
{
type : 'match[password]',
prompt : ''
}
]
},
email: {
identifier : 'email',
rules: [
{
type : 'empty',
prompt : ''
},
{
type : 'email',
prompt : ''
}
]
}
},
inline : true
});
</script>
{{{ template "common/footer" . }}}