diff --git a/internal/configloaders/admin_module.go b/internal/configloaders/admin_module.go
new file mode 100644
index 00000000..ced2a48b
--- /dev/null
+++ b/internal/configloaders/admin_module.go
@@ -0,0 +1,72 @@
+package configloaders
+
+import "github.com/iwind/TeaGo/maps"
+
+type AdminModuleCode = string
+
+const (
+ AdminModuleCodeServer AdminModuleCode = "server"
+ AdminModuleCodeNode AdminModuleCode = "node"
+ AdminModuleCodeDNS AdminModuleCode = "dns"
+ AdminModuleCodeAdmin AdminModuleCode = "admin"
+ AdminModuleCodeLog AdminModuleCode = "log"
+ AdminModuleCodeSetting AdminModuleCode = "setting"
+)
+
+var adminModuleMapping = map[int64]*AdminModuleList{} // adminId => AdminModuleList
+
+func LoadAdminModuleMapping() (map[int64]*AdminModuleList, error) {
+ locker.Lock()
+ defer locker.Unlock()
+
+ if len(adminModuleMapping) > 0 {
+ return adminModuleMapping, nil
+ }
+
+ // TODO
+
+ return nil, nil
+}
+
+func NotifyAdminModuleMappingChange() error {
+ locker.Lock()
+ adminModuleMapping = map[int64]*AdminModuleList{}
+ locker.Unlock() // 这里结束是为了避免和LoadAdminModuleMapping()造成死锁
+ _, err := LoadAdminModuleMapping()
+ return err
+}
+
+func IsAllowModule(adminId int64, module string) bool {
+ // TODO
+ return false
+}
+
+// 所有权限列表
+func AllModuleMaps() []maps.Map {
+ return []maps.Map{
+ {
+ "name": "网站服务",
+ "code": AdminModuleCodeServer,
+ },
+ {
+ "name": "边缘节点",
+ "code": AdminModuleCodeNode,
+ },
+ {
+ "name": "域名解析",
+ "code": AdminModuleCodeDNS,
+ },
+ {
+ "name": "系统用户",
+ "code": AdminModuleCodeAdmin,
+ },
+ {
+ "name": "日志审计",
+ "code": AdminModuleCodeLog,
+ },
+ {
+ "name": "系统设置",
+ "code": AdminModuleCodeSetting,
+ },
+ }
+}
diff --git a/internal/configloaders/admin_module_list.go b/internal/configloaders/admin_module_list.go
new file mode 100644
index 00000000..fb2447e1
--- /dev/null
+++ b/internal/configloaders/admin_module_list.go
@@ -0,0 +1,8 @@
+package configloaders
+
+import "github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
+
+type AdminModuleList struct {
+ IsSuper bool
+ Modules []*systemconfigs.AdminModule
+}
diff --git a/internal/web/actions/default/admins/createPopup.go b/internal/web/actions/default/admins/createPopup.go
new file mode 100644
index 00000000..eaa3a162
--- /dev/null
+++ b/internal/web/actions/default/admins/createPopup.go
@@ -0,0 +1,94 @@
+package admins
+
+import (
+ "encoding/json"
+ "github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
+ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
+ "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
+ "github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
+ "github.com/iwind/TeaGo/actions"
+)
+
+type CreatePopupAction struct {
+ actionutils.ParentAction
+}
+
+func (this *CreatePopupAction) Init() {
+ this.Nav("", "", "")
+}
+
+func (this *CreatePopupAction) RunGet(params struct{}) {
+ this.Data["modules"] = configloaders.AllModuleMaps()
+ this.Show()
+}
+
+func (this *CreatePopupAction) RunPost(params struct {
+ Fullname string
+ Username string
+ Pass1 string
+ Pass2 string
+ ModuleCodes []string
+
+ Must *actions.Must
+ CSRF *actionutils.CSRF
+}) {
+ params.Must.
+ Field("fullname", params.Fullname).
+ Require("请输入系统用户全名")
+
+ params.Must.
+ Field("username", params.Username).
+ Require("请输入登录用户名").
+ Match(`^[a-zA-Z0-9_]+$`, "用户名中只能包含英文、数字或下划线")
+
+ existsResp, err := this.RPC().AdminRPC().CheckAdminUsername(this.AdminContext(), &pb.CheckAdminUsernameRequest{
+ AdminId: 0,
+ Username: params.Username,
+ })
+ if err != nil {
+ this.ErrorPage(err)
+ return
+ }
+ if existsResp.Exists {
+ this.FailField("username", "此用户名已经被别的系统用户使用,请换一个")
+ }
+
+ params.Must.
+ Field("pass1", params.Pass1).
+ Require("请输入登录密码").
+ Field("pass2", params.Pass2).
+ Require("请输入确认登录密码")
+ if params.Pass1 != params.Pass2 {
+ this.FailField("pass2", "两次输入的密码不一致")
+ }
+
+ modules := []*systemconfigs.AdminModule{}
+ for _, code := range params.ModuleCodes {
+ modules = append(modules, &systemconfigs.AdminModule{
+ Code: code,
+ AllowAll: true,
+ Actions: nil, // TODO 后期再开放细粒度控制
+ })
+ }
+ modulesJSON, err := json.Marshal(modules)
+ if err != nil {
+ this.ErrorPage(err)
+ return
+ }
+
+ createResp, err := this.RPC().AdminRPC().CreateAdmin(this.AdminContext(), &pb.CreateAdminRequest{
+ Username: params.Username,
+ Password: params.Pass1,
+ Fullname: params.Fullname,
+ ModulesJSON: modulesJSON,
+ IsSuper: false, // TODO 后期再开放创建超级用户
+ })
+ if err != nil {
+ this.ErrorPage(err)
+ return
+ }
+
+ defer this.CreateLogInfo("创建系统用户 %d", createResp.AdminId)
+
+ this.Success()
+}
diff --git a/internal/web/actions/default/admins/delete.go b/internal/web/actions/default/admins/delete.go
new file mode 100644
index 00000000..87669adb
--- /dev/null
+++ b/internal/web/actions/default/admins/delete.go
@@ -0,0 +1,24 @@
+package admins
+
+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 {
+ AdminId int64
+}) {
+ defer this.CreateLogInfo("删除系统用户 %d", params.AdminId)
+
+ _, err := this.RPC().AdminRPC().DeleteAdmin(this.AdminContext(), &pb.DeleteAdminRequest{AdminId: params.AdminId})
+ if err != nil {
+ this.ErrorPage(err)
+ return
+ }
+
+ this.Success()
+}
diff --git a/internal/web/actions/default/admins/index.go b/internal/web/actions/default/admins/index.go
new file mode 100644
index 00000000..947c4e64
--- /dev/null
+++ b/internal/web/actions/default/admins/index.go
@@ -0,0 +1,49 @@
+package admins
+
+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{}) {
+ countResp, err := this.RPC().AdminRPC().CountAllEnabledAdmins(this.AdminContext(), &pb.CountAllEnabledAdminsRequest{})
+ if err != nil {
+ this.ErrorPage(err)
+ return
+ }
+ page := this.NewPage(countResp.Count)
+ this.Data["page"] = page.AsHTML()
+
+ adminsResp, err := this.RPC().AdminRPC().ListEnabledAdmins(this.AdminContext(), &pb.ListEnabledAdminsRequest{
+ Offset: page.Offset,
+ Size: page.Size,
+ })
+ if err != nil {
+ this.ErrorPage(err)
+ return
+ }
+ adminMaps := []maps.Map{}
+ for _, admin := range adminsResp.Admins {
+ adminMaps = append(adminMaps, maps.Map{
+ "id": admin.Id,
+ "isOn": admin.IsOn,
+ "isSuper": admin.IsSuper,
+ "username": admin.Username,
+ "fullname": admin.Fullname,
+ "createdTime": timeutil.FormatTime("Y-m-d H:i:s", admin.CreatedAt),
+ })
+ }
+ this.Data["admins"] = adminMaps
+
+ this.Show()
+}
diff --git a/internal/web/actions/default/admins/init.go b/internal/web/actions/default/admins/init.go
new file mode 100644
index 00000000..8d2e71f1
--- /dev/null
+++ b/internal/web/actions/default/admins/init.go
@@ -0,0 +1,21 @@
+package admins
+
+import (
+ "github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
+ "github.com/iwind/TeaGo"
+)
+
+func init() {
+ TeaGo.BeforeStart(func(server *TeaGo.Server) {
+ server.
+ Helper(helpers.NewUserMustAuth()).
+ Data("teaMenu", "admins").
+ Prefix("/admins").
+ Get("", new(IndexAction)).
+ GetPost("/createPopup", new(CreatePopupAction)).
+ GetPost("/updatePopup", new(UpdatePopupAction)).
+ Post("/delete", new(DeleteAction)).
+ Post("/updateOn", new(UpdateOnAction)).
+ EndAll()
+ })
+}
diff --git a/internal/web/actions/default/admins/updateOn.go b/internal/web/actions/default/admins/updateOn.go
new file mode 100644
index 00000000..b2199750
--- /dev/null
+++ b/internal/web/actions/default/admins/updateOn.go
@@ -0,0 +1,11 @@
+package admins
+
+import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
+
+type UpdateOnAction struct {
+ actionutils.ParentAction
+}
+
+func (this *UpdateOnAction) RunPost(params struct{}) {
+ this.Success()
+}
diff --git a/internal/web/actions/default/admins/updatePopup.go b/internal/web/actions/default/admins/updatePopup.go
new file mode 100644
index 00000000..ac52658a
--- /dev/null
+++ b/internal/web/actions/default/admins/updatePopup.go
@@ -0,0 +1,129 @@
+package admins
+
+import (
+ "encoding/json"
+ "github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
+ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
+ "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
+ "github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
+ "github.com/iwind/TeaGo/actions"
+ "github.com/iwind/TeaGo/maps"
+)
+
+type UpdatePopupAction struct {
+ actionutils.ParentAction
+}
+
+func (this *UpdatePopupAction) Init() {
+ this.Nav("", "", "")
+}
+
+func (this *UpdatePopupAction) RunGet(params struct {
+ AdminId int64
+}) {
+
+ adminResp, err := this.RPC().AdminRPC().FindEnabledAdmin(this.AdminContext(), &pb.FindEnabledAdminRequest{AdminId: params.AdminId})
+ if err != nil {
+ this.ErrorPage(err)
+ return
+ }
+ admin := adminResp.Admin
+
+ this.Data["admin"] = maps.Map{
+ "id": admin.Id,
+ "fullname": admin.Fullname,
+ "username": admin.Username,
+ }
+
+ moduleMaps := configloaders.AllModuleMaps()
+ for _, m := range moduleMaps {
+ code := m.GetString("code")
+ isChecked := false
+ for _, module := range admin.Modules {
+ if module.Code == code {
+ isChecked = true
+ break
+ }
+ }
+ m["isChecked"] = isChecked
+ }
+ this.Data["modules"] = moduleMaps
+
+ this.Show()
+}
+
+func (this *UpdatePopupAction) RunPost(params struct {
+ AdminId int64
+
+ Fullname string
+ Username string
+ Pass1 string
+ Pass2 string
+ ModuleCodes []string
+
+ Must *actions.Must
+ CSRF *actionutils.CSRF
+}) {
+ defer this.CreateLogInfo("修改系统用户 %d", params.AdminId)
+
+ params.Must.
+ Field("fullname", params.Fullname).
+ Require("请输入系统用户全名")
+
+ params.Must.
+ Field("username", params.Username).
+ Require("请输入登录用户名").
+ Match(`^[a-zA-Z0-9_]+$`, "用户名中只能包含英文、数字或下划线")
+
+ existsResp, err := this.RPC().AdminRPC().CheckAdminUsername(this.AdminContext(), &pb.CheckAdminUsernameRequest{
+ AdminId: params.AdminId,
+ Username: params.Username,
+ })
+ if err != nil {
+ this.ErrorPage(err)
+ return
+ }
+ if existsResp.Exists {
+ this.FailField("username", "此用户名已经被别的系统用户使用,请换一个")
+ }
+
+ if len(params.Pass1) > 0 {
+ params.Must.
+ Field("pass1", params.Pass1).
+ Require("请输入登录密码").
+ Field("pass2", params.Pass2).
+ Require("请输入确认登录密码")
+ if params.Pass1 != params.Pass2 {
+ this.FailField("pass2", "两次输入的密码不一致")
+ }
+ }
+
+ modules := []*systemconfigs.AdminModule{}
+ for _, code := range params.ModuleCodes {
+ modules = append(modules, &systemconfigs.AdminModule{
+ Code: code,
+ AllowAll: true,
+ Actions: nil, // TODO 后期再开放细粒度控制
+ })
+ }
+ modulesJSON, err := json.Marshal(modules)
+ if err != nil {
+ this.ErrorPage(err)
+ return
+ }
+
+ _, err = this.RPC().AdminRPC().UpdateAdmin(this.AdminContext(), &pb.UpdateAdminRequest{
+ AdminId: params.AdminId,
+ Username: params.Username,
+ Password: params.Pass1,
+ Fullname: params.Fullname,
+ ModulesJSON: modulesJSON,
+ IsSuper: false, // TODO 后期再开放创建超级用户
+ })
+ if err != nil {
+ this.ErrorPage(err)
+ return
+ }
+
+ this.Success()
+}
diff --git a/internal/web/actions/default/log/delete.go b/internal/web/actions/default/log/delete.go
index accd97bc..a5ddc1da 100644
--- a/internal/web/actions/default/log/delete.go
+++ b/internal/web/actions/default/log/delete.go
@@ -13,6 +13,9 @@ type DeleteAction struct {
func (this *DeleteAction) RunPost(params struct {
LogId int64
}) {
+ // 记录日志
+ defer this.CreateLogInfo("删除单个操作日志 %d", params.LogId)
+
// 读取配置
config, err := configloaders.LoadLogConfig()
if err != nil {
@@ -23,9 +26,6 @@ func (this *DeleteAction) RunPost(params struct {
this.Fail("已设置不能删除")
}
- // 记录日志
- defer this.CreateLogInfo("删除单个操作日志 %d", params.LogId)
-
// 执行删除
_, err = this.RPC().LogRPC().DeleteLogPermanently(this.AdminContext(), &pb.DeleteLogPermanentlyRequest{LogId: params.LogId})
if err != nil {
diff --git a/internal/web/actions/default/log/settings.go b/internal/web/actions/default/log/settings.go
index dd928428..f2f9a428 100644
--- a/internal/web/actions/default/log/settings.go
+++ b/internal/web/actions/default/log/settings.go
@@ -38,6 +38,8 @@ func (this *SettingsAction) RunPost(params struct {
Must *actions.Must
CSRF *actionutils.CSRF
}) {
+ defer this.CreateLogInfo("修改日志相关配置")
+
capacity := &shared.SizeCapacity{}
err := json.Unmarshal(params.CapacityJSON, capacity)
if err != nil {
diff --git a/internal/web/actions/default/settings/profile/index.go b/internal/web/actions/default/settings/profile/index.go
index 954f520a..d4c5e0a2 100644
--- a/internal/web/actions/default/settings/profile/index.go
+++ b/internal/web/actions/default/settings/profile/index.go
@@ -45,7 +45,7 @@ func (this *IndexAction) RunPost(params struct {
Field("fullname", params.Fullname).
Require("请输入你的姓名")
- _, err := this.RPC().AdminRPC().UpdateAdmin(this.AdminContext(), &pb.UpdateAdminRequest{
+ _, err := this.RPC().AdminRPC().UpdateAdminInfo(this.AdminContext(), &pb.UpdateAdminInfoRequest{
AdminId: this.AdminId(),
Fullname: params.Fullname,
})
diff --git a/internal/web/helpers/user_must_auth.go b/internal/web/helpers/user_must_auth.go
index a6b4e363..1756142b 100644
--- a/internal/web/helpers/user_must_auth.go
+++ b/internal/web/helpers/user_must_auth.go
@@ -205,6 +205,11 @@ func (this *UserMustAuth) modules() []maps.Map {
},
},
},
+ {
+ "code": "admins",
+ "name": "系统用户",
+ "icon": "users",
+ },
{
"code": "log",
"name": "日志审计",
diff --git a/internal/web/import.go b/internal/web/import.go
index b4d7a1b9..76d8e86b 100644
--- a/internal/web/import.go
+++ b/internal/web/import.go
@@ -3,6 +3,7 @@ package web
import (
_ "github.com/TeaOSLab/EdgeAdmin/internal/tasks"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/about"
+ _ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/admins"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/api"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/api/node"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters"
diff --git a/web/views/@default/admins/@menu.html b/web/views/@default/admins/@menu.html
new file mode 100644
index 00000000..2a03639e
--- /dev/null
+++ b/web/views/@default/admins/@menu.html
@@ -0,0 +1,3 @@
+
| 用户名 | +全名 | +创建时间 | +状态 | +操作 | +
|---|---|---|---|---|
| {{admin.username}}
+
+
+ |
+ {{admin.fullname}} | +{{admin.createdTime}} | +
+ |
+ + 修改 删除 + | +
选中后,不能再次修改删除、清理相关设置,防止出现安全问题。
+选中后,不能再次修改删除、清理等相关设置,防止攻击者用来擦除日志。