diff --git a/internal/configloaders/admin_module.go b/internal/configloaders/admin_module.go
index 602083f5..9058555f 100644
--- a/internal/configloaders/admin_module.go
+++ b/internal/configloaders/admin_module.go
@@ -13,7 +13,8 @@ const (
AdminModuleCodeServer AdminModuleCode = "server"
AdminModuleCodeNode AdminModuleCode = "node"
AdminModuleCodeDNS AdminModuleCode = "dns"
- AdminModuleCodeAdmin AdminModuleCode = "admin"
+ AdminModuleCodeAdmin AdminModuleCode = "admin" // 系统用户
+ AdminModuleCodeUser AdminModuleCode = "user" // 平台用户
AdminModuleCodeLog AdminModuleCode = "log"
AdminModuleCodeSetting AdminModuleCode = "setting"
AdminModuleCodeCommon AdminModuleCode = "common" // 只要登录就可以访问的模块
@@ -118,6 +119,11 @@ func AllModuleMaps() []maps.Map {
"code": AdminModuleCodeDNS,
"url": "/dns",
},
+ {
+ "name": "平台用户",
+ "code": AdminModuleCodeUser,
+ "url": "/users",
+ },
{
"name": "系统用户",
"code": AdminModuleCodeAdmin,
diff --git a/internal/rpc/rpc_client.go b/internal/rpc/rpc_client.go
index 3a71cf36..cc48b201 100644
--- a/internal/rpc/rpc_client.go
+++ b/internal/rpc/rpc_client.go
@@ -224,6 +224,10 @@ func (this *RPCClient) ACMETaskRPC() pb.ACMETaskServiceClient {
return pb.NewACMETaskServiceClient(this.pickConn())
}
+func (this *RPCClient) UserRPC() pb.UserServiceClient {
+ return pb.NewUserServiceClient(this.pickConn())
+}
+
// 构造Admin上下文
func (this *RPCClient) Context(adminId int64) context.Context {
ctx := context.Background()
diff --git a/internal/web/actions/default/servers/server/settings/https/requestCertPopup.go b/internal/web/actions/default/servers/server/settings/https/requestCertPopup.go
index 0b17aa63..12c6f255 100644
--- a/internal/web/actions/default/servers/server/settings/https/requestCertPopup.go
+++ b/internal/web/actions/default/servers/server/settings/https/requestCertPopup.go
@@ -113,7 +113,7 @@ func (this *RequestCertPopupAction) RunPost(params struct {
this.ErrorPage(err)
return
}
- defer this.CreateLogInfo("创建ACME用户", createUserResp.AcmeUserId)
+ defer this.CreateLogInfo("创建ACME用户 %d", createUserResp.AcmeUserId)
acmeUserId = createUserResp.AcmeUserId
this.Data["acmeUser"] = maps.Map{
diff --git a/internal/web/actions/default/users/createPopup.go b/internal/web/actions/default/users/createPopup.go
new file mode 100644
index 00000000..c99aa429
--- /dev/null
+++ b/internal/web/actions/default/users/createPopup.go
@@ -0,0 +1,91 @@
+package users
+
+import (
+ "github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
+ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
+ "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
+ "github.com/iwind/TeaGo/actions"
+)
+
+type CreatePopupAction struct {
+ actionutils.ParentAction
+}
+
+func (this *CreatePopupAction) Init() {
+ this.Nav("", "", "")
+}
+
+func (this *CreatePopupAction) RunGet(params struct{}) {
+ this.Show()
+}
+
+func (this *CreatePopupAction) RunPost(params struct {
+ Username string
+ Pass1 string
+ Pass2 string
+ Fullname string
+ Mobile string
+ Tel string
+ Email string
+ Remark string
+
+ Must *actions.Must
+ CSRF *actionutils.CSRF
+}) {
+ params.Must.
+ Field("username", params.Username).
+ Require("请输入用户名").
+ Match(`^[a-zA-Z0-9_]+$`, "用户名中只能含有英文、数字和下划线")
+
+ checkUsernameResp, err := this.RPC().UserRPC().CheckUsername(this.AdminContext(), &pb.CheckUsernameRequest{
+ UserId: 0,
+ Username: params.Username,
+ })
+ if err != nil {
+ this.ErrorPage(err)
+ return
+ }
+ if checkUsernameResp.Exists {
+ this.FailField("username", "此用户名已经被占用,请换一个")
+ }
+
+ params.Must.
+ Field("pass1", params.Pass1).
+ Require("请输入密码").
+ Field("pass2", params.Pass2).
+ Require("请再次输入确认密码").
+ Equal(params.Pass1, "两次输入的密码不一致")
+
+ params.Must.
+ Field("fullname", params.Fullname).
+ Require("请输入全名")
+
+ if len(params.Mobile) > 0 {
+ params.Must.
+ Field("mobile", params.Mobile).
+ Mobile("请输入正确的手机号")
+ }
+ if len(params.Email) > 0 {
+ params.Must.
+ Field("email", params.Email).
+ Email("请输入正确的电子邮箱")
+ }
+
+ createResp, err := this.RPC().UserRPC().CreateUser(this.AdminContext(), &pb.CreateUserRequest{
+ Username: params.Username,
+ Password: params.Pass1,
+ Fullname: params.Fullname,
+ Mobile: params.Mobile,
+ Tel: params.Tel,
+ Email: params.Email,
+ Remark: params.Remark,
+ Source: "admin:" + numberutils.FormatInt64(this.AdminId()),
+ })
+ if err != nil {
+ this.ErrorPage(err)
+ return
+ }
+ defer this.CreateLogInfo("创建用户 %d", createResp.UserId)
+
+ this.Success()
+}
diff --git a/internal/web/actions/default/users/delete.go b/internal/web/actions/default/users/delete.go
new file mode 100644
index 00000000..10c1769d
--- /dev/null
+++ b/internal/web/actions/default/users/delete.go
@@ -0,0 +1,26 @@
+package users
+
+import (
+ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
+ "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
+)
+
+type DeleteAction struct {
+ actionutils.ParentAction
+}
+
+func (this *DeleteAction) RunPost(params struct {
+ UserId int64
+}) {
+ defer this.CreateLogInfo("删除用户 %d", params.UserId)
+
+ // TODO 检查用户是否有未完成的业务
+
+ _, err := this.RPC().UserRPC().DeleteUser(this.AdminContext(), &pb.DeleteUserRequest{UserId: params.UserId})
+ if err != nil {
+ this.ErrorPage(err)
+ return
+ }
+
+ this.Success()
+}
diff --git a/internal/web/actions/default/users/index.go b/internal/web/actions/default/users/index.go
new file mode 100644
index 00000000..46d14b65
--- /dev/null
+++ b/internal/web/actions/default/users/index.go
@@ -0,0 +1,51 @@
+package users
+
+import (
+ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
+ "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
+ "github.com/iwind/TeaGo/maps"
+ timeutil "github.com/iwind/TeaGo/utils/time"
+)
+
+type IndexAction struct {
+ actionutils.ParentAction
+}
+
+func (this *IndexAction) Init() {
+ this.Nav("", "", "")
+}
+
+func (this *IndexAction) RunGet(params struct {
+ Keyword string
+}) {
+ countResp, err := this.RPC().UserRPC().CountAllEnabledUsers(this.AdminContext(), &pb.CountAllEnabledUsersRequest{Keyword: params.Keyword})
+ if err != nil {
+ this.ErrorPage(err)
+ return
+ }
+ count := countResp.Count
+ page := this.NewPage(count)
+ this.Data["page"] = page.AsHTML()
+
+ usersResp, err := this.RPC().UserRPC().ListEnabledUsers(this.AdminContext(), &pb.ListEnabledUsersRequest{Keyword: params.Keyword})
+ if err != nil {
+ this.ErrorPage(err)
+ return
+ }
+ userMaps := []maps.Map{}
+ for _, user := range usersResp.Users {
+ userMaps = append(userMaps, maps.Map{
+ "id": user.Id,
+ "username": user.Username,
+ "isOn": user.IsOn,
+ "fullname": user.Fullname,
+ "email": user.Email,
+ "mobile": user.Mobile,
+ "tel": user.Tel,
+ "createdTime": timeutil.FormatTime("Y-m-d H:i:s", user.CreatedAt),
+ })
+ }
+ this.Data["users"] = userMaps
+
+ this.Show()
+}
diff --git a/internal/web/actions/default/users/init.go b/internal/web/actions/default/users/init.go
new file mode 100644
index 00000000..53a4a83e
--- /dev/null
+++ b/internal/web/actions/default/users/init.go
@@ -0,0 +1,22 @@
+package users
+
+import (
+ "github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
+ "github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
+ "github.com/iwind/TeaGo"
+)
+
+func init() {
+ TeaGo.BeforeStart(func(server *TeaGo.Server) {
+ server.
+ Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeUser)).
+ Data("teaMenu", "users").
+ Prefix("/users").
+ Get("", new(IndexAction)).
+ GetPost("/createPopup", new(CreatePopupAction)).
+ Get("/user", new(UserAction)).
+ GetPost("/update", new(UpdateAction)).
+ Post("/delete", new(DeleteAction)).
+ EndAll()
+ })
+}
diff --git a/internal/web/actions/default/users/update.go b/internal/web/actions/default/users/update.go
new file mode 100644
index 00000000..aa40d5f8
--- /dev/null
+++ b/internal/web/actions/default/users/update.go
@@ -0,0 +1,128 @@
+package users
+
+import (
+ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
+ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/users/userutils"
+ "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
+ "github.com/iwind/TeaGo/actions"
+ "github.com/iwind/TeaGo/maps"
+)
+
+type UpdateAction struct {
+ actionutils.ParentAction
+}
+
+func (this *UpdateAction) Init() {
+ this.Nav("", "", "update")
+}
+
+func (this *UpdateAction) RunGet(params struct {
+ UserId int64
+}) {
+ err := userutils.InitUser(this.Parent(), params.UserId)
+ if err != nil {
+ this.ErrorPage(err)
+ return
+ }
+
+ userResp, err := this.RPC().UserRPC().FindEnabledUser(this.AdminContext(), &pb.FindEnabledUserRequest{UserId: params.UserId})
+ if err != nil {
+ this.ErrorPage(err)
+ return
+ }
+ user := userResp.User
+ if user == nil {
+ this.NotFound("user", params.UserId)
+ return
+ }
+
+ this.Data["user"] = maps.Map{
+ "id": user.Id,
+ "username": user.Username,
+ "fullname": user.Fullname,
+ "email": user.Email,
+ "tel": user.Tel,
+ "remark": user.Remark,
+ "mobile": user.Mobile,
+ "isOn": user.IsOn,
+ }
+
+ this.Show()
+}
+
+func (this *UpdateAction) RunPost(params struct {
+ UserId int64
+ Username string
+ Pass1 string
+ Pass2 string
+ Fullname string
+ Mobile string
+ Tel string
+ Email string
+ Remark string
+ IsOn bool
+
+ Must *actions.Must
+ CSRF *actionutils.CSRF
+}) {
+ defer this.CreateLogInfo("修改用户 %d", params.UserId)
+
+ params.Must.
+ Field("username", params.Username).
+ Require("请输入用户名").
+ Match(`^[a-zA-Z0-9_]+$`, "用户名中只能含有英文、数字和下划线")
+
+ checkUsernameResp, err := this.RPC().UserRPC().CheckUsername(this.AdminContext(), &pb.CheckUsernameRequest{
+ UserId: params.UserId,
+ Username: params.Username,
+ })
+ if err != nil {
+ this.ErrorPage(err)
+ return
+ }
+ if checkUsernameResp.Exists {
+ this.FailField("username", "此用户名已经被占用,请换一个")
+ }
+
+ if len(params.Pass1) > 0 {
+ params.Must.
+ Field("pass1", params.Pass1).
+ Require("请输入密码").
+ Field("pass2", params.Pass2).
+ Require("请再次输入确认密码").
+ Equal(params.Pass1, "两次输入的密码不一致")
+ }
+
+ params.Must.
+ Field("fullname", params.Fullname).
+ Require("请输入全名")
+
+ if len(params.Mobile) > 0 {
+ params.Must.
+ Field("mobile", params.Mobile).
+ Mobile("请输入正确的手机号")
+ }
+ if len(params.Email) > 0 {
+ params.Must.
+ Field("email", params.Email).
+ Email("请输入正确的电子邮箱")
+ }
+
+ _, err = this.RPC().UserRPC().UpdateUser(this.AdminContext(), &pb.UpdateUserRequest{
+ UserId: params.UserId,
+ Username: params.Username,
+ Password: params.Pass1,
+ Fullname: params.Fullname,
+ Mobile: params.Mobile,
+ Tel: params.Tel,
+ Email: params.Email,
+ Remark: params.Remark,
+ IsOn: params.IsOn,
+ })
+ if err != nil {
+ this.ErrorPage(err)
+ return
+ }
+
+ this.Success()
+}
diff --git a/internal/web/actions/default/users/user.go b/internal/web/actions/default/users/user.go
new file mode 100644
index 00000000..9966b2aa
--- /dev/null
+++ b/internal/web/actions/default/users/user.go
@@ -0,0 +1,50 @@
+package users
+
+import (
+ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
+ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/users/userutils"
+ "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
+ "github.com/iwind/TeaGo/maps"
+)
+
+type UserAction struct {
+ actionutils.ParentAction
+}
+
+func (this *UserAction) Init() {
+ this.Nav("", "", "index")
+}
+
+func (this *UserAction) RunGet(params struct {
+ UserId int64
+}) {
+ err := userutils.InitUser(this.Parent(), params.UserId)
+ if err != nil {
+ this.ErrorPage(err)
+ return
+ }
+
+ userResp, err := this.RPC().UserRPC().FindEnabledUser(this.AdminContext(), &pb.FindEnabledUserRequest{UserId: params.UserId})
+ if err != nil {
+ this.ErrorPage(err)
+ return
+ }
+ user := userResp.User
+ if user == nil {
+ this.NotFound("user", params.UserId)
+ return
+ }
+
+ this.Data["user"] = maps.Map{
+ "id": user.Id,
+ "username": user.Username,
+ "fullname": user.Fullname,
+ "email": user.Email,
+ "tel": user.Tel,
+ "remark": user.Remark,
+ "mobile": user.Mobile,
+ "isOn": user.IsOn,
+ }
+
+ this.Show()
+}
diff --git a/internal/web/actions/default/users/userutils/utils.go b/internal/web/actions/default/users/userutils/utils.go
new file mode 100644
index 00000000..e66c9220
--- /dev/null
+++ b/internal/web/actions/default/users/userutils/utils.go
@@ -0,0 +1,25 @@
+package userutils
+
+import (
+ "errors"
+ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
+ "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
+ "github.com/iwind/TeaGo/maps"
+)
+
+// 查找用户基本信息
+func InitUser(p *actionutils.ParentAction, userId int64) error {
+ resp, err := p.RPC().UserRPC().FindEnabledUser(p.AdminContext(), &pb.FindEnabledUserRequest{UserId: userId})
+ if err != nil {
+ return err
+ }
+ if resp.User == nil {
+ return errors.New("not found user")
+ }
+ p.Data["user"] = maps.Map{
+ "id": userId,
+ "fullname": resp.User.Fullname,
+ "username": resp.User.Username,
+ }
+ return nil
+}
diff --git a/internal/web/helpers/user_must_auth.go b/internal/web/helpers/user_must_auth.go
index 18c16b3e..c56b2c81 100644
--- a/internal/web/helpers/user_must_auth.go
+++ b/internal/web/helpers/user_must_auth.go
@@ -215,11 +215,17 @@ func (this *userMustAuth) modules(adminId int64) []maps.Map {
},
},
},
+ {
+ "code": "users",
+ "module": configloaders.AdminModuleCodeUser,
+ "name": "平台用户",
+ "icon": "users",
+ },
{
"code": "admins",
"module": configloaders.AdminModuleCodeAdmin,
"name": "系统用户",
- "icon": "users",
+ "icon": "user secret",
},
{
"code": "log",
diff --git a/internal/web/import.go b/internal/web/import.go
index 76d8e86b..0dce5748 100644
--- a/internal/web/import.go
+++ b/internal/web/import.go
@@ -83,4 +83,5 @@ import (
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/upgrade"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/setup"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/ui"
+ _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/users"
)
diff --git a/web/views/@default/users/@user_menu.html b/web/views/@default/users/@user_menu.html
new file mode 100644
index 00000000..f43bfbf9
--- /dev/null
+++ b/web/views/@default/users/@user_menu.html
@@ -0,0 +1,6 @@
+
暂时还没有用户。
+| 用户名 | +全名 | +手机号 | +注册时间 | +状态 | +操作 | +
|---|---|---|---|---|---|
| {{user.username}} | +{{user.fullname}} | ++ {{user.mobile}} + - + | +{{user.createdTime}} | +
+ |
+ + 详情 + 删除 + | +
| 状态 | +
+ |
+
| 用户名 | ++ {{user.username}} + | +
| 全名 | ++ {{user.fullname}} + | +
| 手机号 | ++ {{user.mobile}} + - + | +
| 联系电话 | ++ {{user.tel}} + - + | +
| 电子邮箱 | ++ {{user.email}} + - + | +
| 备注 | ++ {{user.remark}} + - + | +